diff --git a/sources/css/main.css b/sources/css/main.css index 359cbad..412313e 100644 --- a/sources/css/main.css +++ b/sources/css/main.css @@ -223,20 +223,24 @@ button::-moz-focus-inner { background: #222222; } -.menu-icon-only { - padding-left: 12px; - padding-right: 12px; -} - .menu-right { float: right !important; } -.menu-item i, .menu-trigger i, .menu-btn i { +.menu-item i, .menu-trigger i, .menu-button i { margin-right: 6px; font-size: 14px; } +.menu-icon-only { + padding-left: 12px; + padding-right: 12px; +} + +.menu-icon-only i { + margin-right: 0px; +} + .menu-trigger.open { padding-top: 4px; font-size: 0px; @@ -245,6 +249,65 @@ button::-moz-focus-inner { font-size: 14px; } +.box { + position: absolute; + z-index: 1000; + background: #1c1c1c; + -webkit-box-shadow: 0px 0px 16px 0px #333; + box-shadow: 0px 0px 16px 0px #333; + border: 1px solid #090909; + display: none; +} + +.box h1 { + margin: 0; + padding: 12px; + padding-top: 16px; + color: #040404; + text-shadow: 0px 1px 0px #333; + filter: dropshadow(color=#333, offx=0, offy=1); +} + +.box .close { + color: #040404; + text-shadow: 0px 1px 0px #333; + filter: dropshadow(color=#333, offx=0, offy=1); + font-size: 16px; + padding: 4px; + cursor: pointer; + position: absolute; + top: 8px; + right: 6px; + background: none; + border: none; +} + +.box .close:hover { + color: #666; +} + +#box-share { + z-index: 1001; + width: 400px; + top: 50%; + left: 50%; + margin-top: -50px; + margin-left: -200px; +} + +#box-share-url { + margin: 12px; + margin-top: 0px; + background: #111; + border: none; + width: 356px; + line-height: 40px; + height: 40px; + color: #999; + padding: 0px 10px; + -webkit-box-shadow: inset 0px 4px 6px 0px #060606, 0px 1px 0px 0px #333333; + box-shadow: inset 0px 4px 6px 0px #060606, 0px 1px 0px 0px #333333; +} .ace_gutter { -webkit-touch-callout: none; diff --git a/sources/editor.html b/sources/editor.html index ba2345b..b671867 100644 --- a/sources/editor.html +++ b/sources/editor.html @@ -19,10 +19,6 @@ - + + + +
+

Share URL

+
+ +
+ +
+
diff --git a/sources/shdr/App.coffee b/sources/shdr/App.coffee index 5798bd9..f505dcb 100644 --- a/sources/shdr/App.coffee +++ b/sources/shdr/App.coffee @@ -9,6 +9,7 @@ class App constructor: (domEditor, domCanvas, conf={}) -> # CUSTOM THREE.JS HACK window.THREE_SHADER_OVERRIDE = true + @initBaseurl() @documents = ['', ''] @marker = null @conf = @@ -19,10 +20,19 @@ class App @viewer = new shdr.Viewer(@byId(domCanvas)) @validator = new shdr.Validator(@viewer.canvas) @initEditor(domEditor) + @initFromURL() @byId(domEditor).addEventListener('keyup', ((e) => @onEditorKeyUp(e)), off) @byId(domEditor).addEventListener('keydown', ((e) => @onEditorKeyDown(e)), off) @loop() + initBaseurl: -> + url = window.location.href + hash = url.indexOf('#') + if hash > 0 + @baseurl = url.substr(0, hash) + else + @baseurl = url + initEditor: (domEditor) -> @documents[App.FRAGMENT] = @viewer.fs @documents[App.VERTEX] = @viewer.vs @@ -66,6 +76,88 @@ class App @ui.setStatus("Line #{line} : #{msg}", shdr.UI.ERROR) + initFromURL: -> + obj = @unpackURL() + if obj and obj.documents and obj.documents.length is 2 + @documents = obj.documents + fs = @documents[App.FRAGMENT] + vs = @documents[App.VERTEX] + [_fs, fl, fm] = @validator.validate(fs, shdr.Validator.FRAGMENT) + [_vs, vl, vm] = @validator.validate(vs, shdr.Validator.VERTEX) + if _fs and _vs + @viewer.updateShader(vs, App.VERTEX) + @viewer.updateShader(fs, App.FRAGMENT) + @editor.getSession().setValue(if @conf.mode is App.VERTEX then vs else fs) + @ui.setMenuMode(App.FRAGMENT) + @ui.setStatus("Shaders successfully loaded and compiled from URL.", + shdr.UI.SUCCESS) + else if _vs + @viewer.updateShader(vs, App.VERTEX) + @setMode(App.FRAGMENT, true) + @ui.setMenuMode(App.FRAGMENT) + @ui.setStatus("Shaders loaded from URL but Fragment could not compile. Line #{fl} : #{fm}", + shdr.UI.WARNING) + else if _fs + @viewer.updateShader(fs, App.FRAGMENT) + @setMode(App.VERTEX, true) + @ui.setMenuMode(App.VERTEX) + @ui.setStatus("Shaders loaded from URL but Vertex could not compile. Line #{vl} : #{vm}", + shdr.UI.WARNING) + else + @setMode(App.VERTEX, true) + @ui.setMenuMode(App.VERTEX) + @ui.setStatus("Shaders loaded from URL but could not compile. Line #{vl} : #{vm}", + shdr.UI.WARNING) + @editor.focus() + true + else + false + + packURL: -> + try + obj = + documents: @documents + model: @viewer.currentModel + json = JSON.stringify(obj) + packed = window.btoa(RawDeflate.deflate(json)) + return @baseurl + '#1/' + packed + catch e + @ui.setStatus("Unable to pack document: #{e.getMessage?()}", + shdr.UI.WARNING) + + unpackURL: -> + return false if not window.location.hash + try + hash = window.location.hash.substr(1) + version = hash.substr(0, 2) + packed = hash.substr(2) + json = RawDeflate.inflate(window.atob(packed)) + obj = JSON.parse(json) + return obj + catch e + @ui.setStatus("Unable to unpack document: #{e.getMessage?()}", + shdr.UI.WARNING) + + download: -> + try + blob = new Blob([@editor.getSession().getValue()], + type: 'text/plain' + ) + url = URL.createObjectURL(blob) + win = window.open(url, '_blank') + if win + win.focus() + else + @ui.setStatus('Your browser as blocked the download, please disable popup blocker.', + shdr.UI.WARNING) + catch e + @ui.setStatus('Your browser does not support Blob, unable to download.', + shdr.UI.WARNING) + url + + updateDocument: -> + @documents[@conf.mode] = @editor.getSession().getValue() + onEditorKeyUp: (e) -> key = e.keyCode proc = @conf.update is App.UPDATE_ENTER and key is 13 @@ -89,19 +181,20 @@ class App @conf.update = parseInt(mode) this - setMode: (mode=App.FRAGMENT) -> + setMode: (mode=App.FRAGMENT, force=false) -> mode = parseInt(mode) - return false if @conf.mode is mode + return false if @conf.mode is mode and not force old = @conf.mode @conf.mode = mode session = @editor.getSession() switch mode when App.FRAGMENT - @documents[old] = session.getValue() + @documents[old] = session.getValue() if not force session.setValue(@documents[App.FRAGMENT]) when App.VERTEX - @documents[old] = session.getValue() + @documents[old] = session.getValue() if not force session.setValue(@documents[App.VERTEX]) + @updateShader() this byId: (id) -> diff --git a/sources/shdr/App.js b/sources/shdr/App.js index 722f319..a761c00 100644 --- a/sources/shdr/App.js +++ b/sources/shdr/App.js @@ -20,6 +20,7 @@ conf = {}; } window.THREE_SHADER_OVERRIDE = true; + this.initBaseurl(); this.documents = ['', '']; this.marker = null; this.conf = { @@ -31,6 +32,7 @@ this.viewer = new shdr.Viewer(this.byId(domCanvas)); this.validator = new shdr.Validator(this.viewer.canvas); this.initEditor(domEditor); + this.initFromURL(); this.byId(domEditor).addEventListener('keyup', (function(e) { return _this.onEditorKeyUp(e); }), false); @@ -40,6 +42,17 @@ this.loop(); } + App.prototype.initBaseurl = function() { + var hash, url; + url = window.location.href; + hash = url.indexOf('#'); + if (hash > 0) { + return this.baseurl = url.substr(0, hash); + } else { + return this.baseurl = url; + } + }; + App.prototype.initEditor = function(domEditor) { this.documents[App.FRAGMENT] = this.viewer.fs; this.documents[App.VERTEX] = this.viewer.vs; @@ -93,6 +106,98 @@ } }; + App.prototype.initFromURL = function() { + var fl, fm, fs, obj, vl, vm, vs, _fs, _ref, _ref1, _vs; + obj = this.unpackURL(); + if (obj && obj.documents && obj.documents.length === 2) { + this.documents = obj.documents; + fs = this.documents[App.FRAGMENT]; + vs = this.documents[App.VERTEX]; + _ref = this.validator.validate(fs, shdr.Validator.FRAGMENT), _fs = _ref[0], fl = _ref[1], fm = _ref[2]; + _ref1 = this.validator.validate(vs, shdr.Validator.VERTEX), _vs = _ref1[0], vl = _ref1[1], vm = _ref1[2]; + if (_fs && _vs) { + this.viewer.updateShader(vs, App.VERTEX); + this.viewer.updateShader(fs, App.FRAGMENT); + this.editor.getSession().setValue(this.conf.mode === App.VERTEX ? vs : fs); + this.ui.setMenuMode(App.FRAGMENT); + this.ui.setStatus("Shaders successfully loaded and compiled from URL.", shdr.UI.SUCCESS); + } else if (_vs) { + this.viewer.updateShader(vs, App.VERTEX); + this.setMode(App.FRAGMENT, true); + this.ui.setMenuMode(App.FRAGMENT); + this.ui.setStatus("Shaders loaded from URL but Fragment could not compile. Line " + fl + " : " + fm, shdr.UI.WARNING); + } else if (_fs) { + this.viewer.updateShader(fs, App.FRAGMENT); + this.setMode(App.VERTEX, true); + this.ui.setMenuMode(App.VERTEX); + this.ui.setStatus("Shaders loaded from URL but Vertex could not compile. Line " + vl + " : " + vm, shdr.UI.WARNING); + } else { + this.setMode(App.VERTEX, true); + this.ui.setMenuMode(App.VERTEX); + this.ui.setStatus("Shaders loaded from URL but could not compile. Line " + vl + " : " + vm, shdr.UI.WARNING); + } + this.editor.focus(); + return true; + } else { + return false; + } + }; + + App.prototype.packURL = function() { + var json, obj, packed; + try { + obj = { + documents: this.documents, + model: this.viewer.currentModel + }; + json = JSON.stringify(obj); + packed = window.btoa(RawDeflate.deflate(json)); + return this.baseurl + '#1/' + packed; + } catch (e) { + return this.ui.setStatus("Unable to pack document: " + (typeof e.getMessage === "function" ? e.getMessage() : void 0), shdr.UI.WARNING); + } + }; + + App.prototype.unpackURL = function() { + var hash, json, obj, packed, version; + if (!window.location.hash) { + return false; + } + try { + hash = window.location.hash.substr(1); + version = hash.substr(0, 2); + packed = hash.substr(2); + json = RawDeflate.inflate(window.atob(packed)); + obj = JSON.parse(json); + return obj; + } catch (e) { + return this.ui.setStatus("Unable to unpack document: " + (typeof e.getMessage === "function" ? e.getMessage() : void 0), shdr.UI.WARNING); + } + }; + + App.prototype.download = function() { + var blob, url, win; + try { + blob = new Blob([this.editor.getSession().getValue()], { + type: 'text/plain' + }); + url = URL.createObjectURL(blob); + win = window.open(url, '_blank'); + if (win) { + win.focus(); + } else { + this.ui.setStatus('Your browser as blocked the download, please disable popup blocker.', shdr.UI.WARNING); + } + } catch (e) { + this.ui.setStatus('Your browser does not support Blob, unable to download.', shdr.UI.WARNING); + } + return url; + }; + + App.prototype.updateDocument = function() { + return this.documents[this.conf.mode] = this.editor.getSession().getValue(); + }; + App.prototype.onEditorKeyUp = function(e) { var key, proc; key = e.keyCode; @@ -129,13 +234,16 @@ return this; }; - App.prototype.setMode = function(mode) { + App.prototype.setMode = function(mode, force) { var old, session; if (mode == null) { mode = App.FRAGMENT; } + if (force == null) { + force = false; + } mode = parseInt(mode); - if (this.conf.mode === mode) { + if (this.conf.mode === mode && !force) { return false; } old = this.conf.mode; @@ -143,13 +251,18 @@ session = this.editor.getSession(); switch (mode) { case App.FRAGMENT: - this.documents[old] = session.getValue(); + if (!force) { + this.documents[old] = session.getValue(); + } session.setValue(this.documents[App.FRAGMENT]); break; case App.VERTEX: - this.documents[old] = session.getValue(); + if (!force) { + this.documents[old] = session.getValue(); + } session.setValue(this.documents[App.VERTEX]); } + this.updateShader(); return this; }; diff --git a/sources/shdr/UI.coffee b/sources/shdr/UI.coffee index c9389d6..8c83d06 100644 --- a/sources/shdr/UI.coffee +++ b/sources/shdr/UI.coffee @@ -10,6 +10,8 @@ class UI @initModels() @initMenus() @initToggles() + @initButtons() + @initBoxes() initStatus: -> el = $('#status') @@ -22,6 +24,19 @@ class UI icon: icon content: content + initBoxes: -> + @boxes = + share: $('#box-share') + @boxes.share.find('#box-share-url').on('click', (e) -> + $(this).select() + ) + $('.box .close').on('click', (e) -> + $(this).parent().fadeOut(200) + ) + + initButtons: -> + $('.menu-button').on('click', (e) => @onButton(e)) + initToggles: -> $('.menu-toggle').on('click', (e) => @onToggle(e)) @@ -59,6 +74,14 @@ class UI @status.icon.addClass('icon-warning-sign') @status.content.text(message) + setMenuMode: (mode) -> + el = $('#menu-mode') + item = el.find("button[data-index=#{mode}]") + if item + el.find('.menu-trigger').children('span') + .text(item.text()) + mode + onToggle: (event) -> el = $(event.target) root = el.parent() @@ -75,6 +98,12 @@ class UI @[root.attr('data-action')+'Action']?(state, null, el) @app.editor.focus() + onButton: (event) -> + el = $(event.target) + root = el.parent() + @[root.attr('data-action')+'Action']?(null, null, el) + el.blur() + onMenuTrigger: (event) -> el = $(event.target) root = el.parent() @@ -131,5 +160,14 @@ class UI rotateAction: (state) -> @app.viewer.rotate = state + shareAction: -> + @app.updateDocument() + url = @app.packURL() + @boxes.share.find('#box-share-url').val(url) + @boxes.share.fadeIn(200) + + downloadAction: -> + @app.download() + @shdr ||= {} @shdr.UI = UI \ No newline at end of file diff --git a/sources/shdr/UI.js b/sources/shdr/UI.js index d5f3d2a..3a2f2e7 100644 --- a/sources/shdr/UI.js +++ b/sources/shdr/UI.js @@ -17,6 +17,8 @@ this.initModels(); this.initMenus(); this.initToggles(); + this.initButtons(); + this.initBoxes(); } UI.prototype.initStatus = function() { @@ -33,6 +35,25 @@ }; }; + UI.prototype.initBoxes = function() { + this.boxes = { + share: $('#box-share') + }; + this.boxes.share.find('#box-share-url').on('click', function(e) { + return $(this).select(); + }); + return $('.box .close').on('click', function(e) { + return $(this).parent().fadeOut(200); + }); + }; + + UI.prototype.initButtons = function() { + var _this = this; + return $('.menu-button').on('click', function(e) { + return _this.onButton(e); + }); + }; + UI.prototype.initToggles = function() { var _this = this; return $('.menu-toggle').on('click', function(e) { @@ -94,6 +115,16 @@ return this.status.content.text(message); }; + UI.prototype.setMenuMode = function(mode) { + var el, item; + el = $('#menu-mode'); + item = el.find("button[data-index=" + mode + "]"); + if (item) { + el.find('.menu-trigger').children('span').text(item.text()); + } + return mode; + }; + UI.prototype.onToggle = function(event) { var el, ico, root, state, _name; el = $(event.target); @@ -115,6 +146,16 @@ return this.app.editor.focus(); }; + UI.prototype.onButton = function(event) { + var el, root, _name; + el = $(event.target); + root = el.parent(); + if (typeof this[_name = root.attr('data-action') + 'Action'] === "function") { + this[_name](null, null, el); + } + return el.blur(); + }; + UI.prototype.onMenuTrigger = function(event) { var el, list, root, _this = this; @@ -187,6 +228,18 @@ return this.app.viewer.rotate = state; }; + UI.prototype.shareAction = function() { + var url; + this.app.updateDocument(); + url = this.app.packURL(); + this.boxes.share.find('#box-share-url').val(url); + return this.boxes.share.fadeIn(200); + }; + + UI.prototype.downloadAction = function() { + return this.app.download(); + }; + return UI; })(); diff --git a/sources/shdr/Viewer.coffee b/sources/shdr/Viewer.coffee index 968a2bb..aabdbc3 100644 --- a/sources/shdr/Viewer.coffee +++ b/sources/shdr/Viewer.coffee @@ -6,6 +6,7 @@ class Viewer constructor: (@dom) -> @time = 0.0 @rotate = true + @currentModel = null @rotateRate = 0.005 @renderer = new THREE.WebGLRenderer(antialias: on) @canvas = @renderer.domElement @@ -44,6 +45,7 @@ class Viewer ) initModel: (geo, key) -> + @currentModel = key data = shdr.Models[key] if @model? old = @model.geometry diff --git a/sources/shdr/Viewer.js b/sources/shdr/Viewer.js index 2c8b164..9f29248 100644 --- a/sources/shdr/Viewer.js +++ b/sources/shdr/Viewer.js @@ -13,6 +13,7 @@ this.dom = dom; this.time = 0.0; this.rotate = true; + this.currentModel = null; this.rotateRate = 0.005; this.renderer = new THREE.WebGLRenderer({ antialias: true @@ -65,6 +66,7 @@ Viewer.prototype.initModel = function(geo, key) { var data, old; + this.currentModel = key; data = shdr.Models[key]; if (this.model != null) { old = this.model.geometry;