Commit 2a352bb9 authored by Steffen Michels's avatar Steffen Michels

Merge branch 'scaled-editor-extension' into 'master'

Added an extension to create scaled editors

See merge request !282
parents e463865a 17cb3483
Pipeline #26103 passed with stage
in 5 minutes and 16 seconds
definition module iTasks.Extensions.ScaledEditor
from iTasks.UI.Editor import :: Editor
/**
* This module wraps any editor in a container.
* The editor gets a fixed size, but is scaled down or up with css transforms to always fit
* the container. This is useful for presentations and other applications where you need a
* predictable fixed UI size, but don't know the resolution of the display.
*/
scaledEditor :: Int Int (Editor a) -> Editor a
implementation module iTasks.Extensions.ScaledEditor
import StdFunctions, StdString, StdList, StdBool, StdArray
import Text.HTML, Text.GenJSON, Data.Error, Data.Func
import iTasks.UI.Definition
import iTasks.UI.Editor
import iTasks.UI.Editor.Modifiers
import iTasks.UI.JavaScript
import iTasks.Internal.Serialization
import qualified Data.Map as DM
//Basic idea:
//- Give the inner editor an exact size in pixels
//- Wrap the editor in a container
//- Add an onResize handler on the container that measures both the outer
// and inner element and sets a CSS transform on the inner element.
scaledEditor :: Int Int (Editor a) -> Editor a
scaledEditor width height editor = {Editor|genUI=genUI,onEdit=onEdit,onRefresh=onRefresh,valueFromState=valueFromState}
where
fixedEditor = (sizeAttr (ExactSize width) (ExactSize height)) @>> editor
genUI attr datapath mode vst = case fixedEditor.Editor.genUI attr datapath (mapEditMode id mode) vst of
(Ok (editorUI,editorState),vst)
# (initUIString, vst) = serializeForClient (wrapInitUIFunction initUI) vst
= (Ok (wrapUI initUIString editorUI,editorState),vst)
(Error e,vst) = (Error e,vst)
onEdit datapath event state vst = case fixedEditor.Editor.onEdit datapath event state vst of
(Ok (change,state),vst)
# (initUIString, vst) = serializeForClient (wrapInitUIFunction initUI) vst
= (Ok (wrapChange initUIString change,state),vst)
(Error e,vst) = (Error e,vst)
onRefresh datapath value state vst = case fixedEditor.Editor.onRefresh datapath value state vst of
(Ok (change,state),vst)
# (initUIString, vst) = serializeForClient (wrapInitUIFunction initUI) vst
= (Ok (wrapChange initUIString change,state),vst)
(Error e,vst) = (Error e,vst)
valueFromState = fixedEditor.Editor.valueFromState
wrapUI initUI ui = uiac UIContainer ('DM'.fromList [("initUI",JSONString initUI)]) [ui]
wrapChange initUI NoChange = NoChange
wrapChange initUI (ReplaceUI ui) = ReplaceUI (wrapUI initUI ui)
wrapChange initUI change = ChangeUI [] [(0,ChangeChild change)]
//Add the onResize event handler on the wrapping container to scale the inner element
initUI me world
# (jsOnResize,world) = jsWrapFun (onResize me) me world
# world = (me .# "onResize" .= jsOnResize) world
= world
onResize me args world
//Select the inner editor's dom element
# (children,world) = (me .# "domEl.children") .? world
# (innerEl,world) = (children .# 0) .? world
//Measure the inner size of the container element
# (domElClientHeight,world) = (me .# "domEl.clientHeight") .? world
# (domElClientWidth,world) = (me .# "domEl.clientWidth") .? world
//Measure the outer size of the editor's element
# (innerElOffsetHeight,world) = (innerEl .# "offsetHeight") .? world
# (innerElOffsetWidth,world) = (innerEl .# "offsetWidth") .? world
//Determine the scale factor
# scaleHeight = toReal (fromMaybe 1 (jsValToInt domElClientHeight)) / toReal (fromMaybe 1 (jsValToInt innerElOffsetHeight))
# scaleWidth = toReal (fromMaybe 1 (jsValToInt domElClientWidth)) / toReal (fromMaybe 1 (jsValToInt innerElOffsetWidth))
# scale = min scaleHeight scaleWidth
# world = (innerEl .# "style.transformOrigin" .= "top left") world
# world = (innerEl .# "style.transform" .= ("scale(" +++ toString scale +++ ")")) world
= world
......@@ -99,6 +99,10 @@ itasks.Panel = {
case 'right': me.domEl.style['width'] = (startSize + (ev.clientX - startPos)) + 'px'; break;
case 'left': me.domEl.style['width'] = (startSize + (startPos - ev.clientX)) + 'px'; break;
}
//Trigger resize event on all siblings
me.parentCmp.children.forEach(function(child) {
child.onResize();
});
};
window.addEventListener('mousemove', resize, false);
......
......@@ -391,6 +391,8 @@ itasks.Viewport = {
//Create a temporary root element
me.insertChild(0,{type:'Loader', parentCmp: me});
me.parentViewport = me.getViewport();
//Get a connection
me.taskUrl = me.determineTaskEndpoint();
me.connection = itasks.ConnectionPool.getConnection(me.taskUrl);
......@@ -414,16 +416,15 @@ itasks.Viewport = {
uiChangeCallback,
exceptionCallback);
}
me.addWindowResizeListener();
},
getParentViewport: function() {
var me = this, parentVp = me.parentCmp;
while(parentVp) {
if(parentVp.cssCls == 'viewport') { //Bit of a hack...
return parentVp;
}
parentVp = parentVp.parentCmp;
addWindowResizeListener: function() {
var me = this;
if(me.parentViewport !== null) { //Only listen to window changes as the top level
return;
}
return null;
window.addEventListener('resize',me.onResize.bind(me));
},
determineTaskEndpoint: function() {
var me = this;
......@@ -434,12 +435,11 @@ itasks.Viewport = {
return 'ws://' + location.host + me.taskUrl + (me.taskUrl.endsWith('/') ? '' : '/') + 'gui-wsock';
}
}
var parentVp = me.getParentViewport();
if(parentVp === null) {
if(me.parentViewport === null) {
//If there is no parent, use the default url
return 'ws://' + location.host + location.pathname + (location.pathname.endsWith('/') ? '' : '/') + 'gui-wsock';
} else {
return parentVp.determineTaskEndpoint();
return me.parentViewport.determineTaskEndpoint();
}
},
doEditEvent: function (taskId, editorId, value) {
......
module TestScaledEditor
import iTasks
import iTasks.Extensions.ScaledEditor
import StdFunctions
import Text.HTML
test = (testHtmlFrame -&&- testResizable) <<@ AddCSSClass "itasks-horizontal"
testHtmlFrame :: Task HtmlTag
testHtmlFrame = viewInformation () [ViewUsing id (scaledEditor 300 200 (htmlView <<@ styleAttr "padding: 0"))] html
<<@ ApplyLayout (sequenceLayouts [layoutSubUIs (SelectByPath [1]) (setUIAttributes (sizeAttr FlexSize FlexSize))
,setUIAttributes (sizeAttr FlexSize FlexSize)
]
)
where
html = DivTag [StyleAttr "width: 100%; height: 100%; background: blue; color: white; margin: 0"]
[SpanTag [StyleAttr "font-size: 20px"] [Text "This text is automatically scaled"]
]
testResizable :: Task String
testResizable = viewInformation () [] "RESIZE THIS PANEL"
<<@ ApplyLayout (sequenceLayouts[setUIType UIPanel,setUIAttributes (resizableAttr [LeftSide])])
Start world = doTasks test world
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment