diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..1932038 --- /dev/null +++ b/build.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-widget-easyslides10.iml b/plugin-widget-easyslides10.iml new file mode 100644 index 0000000..cd779d8 --- /dev/null +++ b/plugin-widget-easyslides10.iml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..75ef9f5 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,26 @@ + + + com.fr.plugin.easyslides.v10 + + yes + 1.0 + 10.0 + 2019-11-1 + Felix + + [2019-11-21]Felix:适配新的API

+ ]]>
+ + + + + + + + + + + +
+ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3f29590 --- /dev/null +++ b/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + com.fr.plugin + starter + 10.0 + + + jar + plugin-widget-simpleppt10 + \ No newline at end of file diff --git a/specific.md b/specific.md new file mode 100644 index 0000000..27f6bf2 --- /dev/null +++ b/specific.md @@ -0,0 +1,16 @@ +##UI组成 +- 数据源:一列,每行为一页slide的文字内容,其中可以用h5标签 +- 幻灯片样式选择列表 +- 背景颜色 +- 可选项:1、自动/手动播放;2、自动播放间隔时间 + +##幻灯片样式 +- 左入,右入 +- 上入,下入 +- 前入,后入 + +##比例 +- slide width:900->1536 padding:40 +- slide height:700->758 padding:60 +- slide fontsize:30->(1536*758) +- slide line-height:36->(1536*758) \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/easyslides/Constants.java b/src/main/java/com/fr/plugin/easyslides/Constants.java new file mode 100644 index 0000000..6bd71a6 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/Constants.java @@ -0,0 +1,6 @@ +package com.fr.plugin.easyslides; + +public class Constants { + public static final String PLUGIN_ID = "com.fr.plugin.easyslides"; + public static final String WIDGET_TYPE = "easyslides"; +} diff --git a/src/main/java/com/fr/plugin/easyslides/CssFileLoader.java b/src/main/java/com/fr/plugin/easyslides/CssFileLoader.java new file mode 100644 index 0000000..5b61bd2 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/CssFileLoader.java @@ -0,0 +1,10 @@ +package com.fr.plugin.easyslides; + +import com.fr.stable.fun.impl.AbstractCssFileHandler; + +public class CssFileLoader extends AbstractCssFileHandler { + @Override + public String[] pathsForFiles() { + return new String[]{"/com/fr/plugin/easyslides/web/impress-default.css"}; + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/EasySlidesWidget.java b/src/main/java/com/fr/plugin/easyslides/EasySlidesWidget.java new file mode 100644 index 0000000..d488d5e --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/EasySlidesWidget.java @@ -0,0 +1,249 @@ +package com.fr.plugin.easyslides; + +import com.fr.base.Utils; +import com.fr.form.ui.DataControl; +import com.fr.form.ui.DirectWriteEditor; +import com.fr.form.ui.Widget; +import com.fr.form.ui.WidgetValue; +import com.fr.form.ui.concept.data.ValueInitializer; +import com.fr.json.JSONArray; +import com.fr.json.JSONException; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.easyslides.slidestyles.SlideStyle; +import com.fr.plugin.easyslides.slidestyles.StyleFactory; +import com.fr.plugin.easyslides.ui.SlideStyleEditor; +import com.fr.script.Calculator; +import com.fr.stable.core.NodeVisitor; +import com.fr.stable.script.CalculatorProvider; +import com.fr.stable.web.Repository; +import com.fr.stable.xml.XMLPrintWriter; +import com.fr.stable.xml.XMLableReader; + +import java.awt.*; +import java.util.List; + +import static com.fr.plugin.easyslides.Util.colorValueToHexString; + +public class EasySlidesWidget extends DirectWriteEditor implements DataControl { + public static final String BACKGROUND_COLOR = "backgroundColor"; + public static final String AUTO_PLAY = "autoPlay"; + public static final String AUTO_PLAY_INTERVAL_TIME = "autoPlayIntervalTime"; + public static final String SLIDE_STYLE = "slideStyle"; + public static final String TOOLBAR = "toolBar"; + public static final String PROGRESSBAR = "progressBar"; + public static final String PROGRESS = "progress"; + + private ValueInitializer widgetValue; + private Object backgroundColor; + private boolean autoPlay; + private int autoPlayIntervalTime; + private String slideStyle; + private boolean toolBar; + private boolean progressBar; + private boolean progress; + + public EasySlidesWidget() { + widgetValue = new WidgetValue(); + //autoPlay = true; + setAutoPlayIntervalTime(5); + backgroundColor = new Color(215, 215, 215); + slideStyle = SlideStyleEditor.DEFAULT_STYLE; + } + + public Object getBackgroundColor() { + return backgroundColor; + } + + public void setBackgroundColor(Object backgroundColor) { + this.backgroundColor = backgroundColor; + } + + public boolean isAutoPlay() { + return autoPlay; + } + + public void setAutoPlay(boolean autoPlay) { + this.autoPlay = autoPlay; + if (getAutoPlayIntervalTime() <= 0) { + setAutoPlayIntervalTime(5); + } + } + + public int getAutoPlayIntervalTime() { + return autoPlayIntervalTime; + } + + public void setAutoPlayIntervalTime(int autoPlayIntervalTime) { + this.autoPlayIntervalTime = autoPlayIntervalTime; + if (autoPlayIntervalTime > 0) { + setAutoPlay(true); + } else { + setAutoPlay(false); + } + } + + public String getSlideStyle() { + return slideStyle; + } + + public void setSlideStyle(String slideStyle) { + this.slideStyle = slideStyle; + } + + public boolean isToolBar() { + return toolBar; + } + + public void setToolBar(boolean toolBar) { + this.toolBar = toolBar; + } + + public boolean isProgressBar() { + return progressBar; + } + + public void setProgressBar(boolean progressBar) { + this.progressBar = progressBar; + } + + public boolean isProgress() { + return progress; + } + + public void setProgress(boolean progress) { + this.progress = progress; + } + + @Override + public String[] supportedEvents() { + return new String[0]; + } + + @Override + public int[] getValueType() { + return new int[]{TYPE_DATABINDING}; + } + + @Override + public void setWidgetValue(ValueInitializer valueInitializer) { + widgetValue = valueInitializer; + } + + @Override + public ValueInitializer getWidgetValue() { + return widgetValue; + } + + @Override + public void createValueResult(CalculatorProvider calculatorProvider, JSONObject jsonObject) { + if (this.getWidgetValue() != null) { + calculatorProvider.setAttribute(Widget.NAME, this.getWidgetName().toUpperCase()); + calculatorProvider.setAttribute(ValueInitializer.RETURN_ARRAY, true); + calculatorProvider.setAttribute(ValueInitializer.VALUE_REPEAT, true); + Object result = getWidgetValue().executeResult(calculatorProvider); + try { + jsonObject.put(this.widgetName.toUpperCase(), result == null ? "" : result); + } catch (JSONException e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + } + + @Override + public String getFormatText() { + return ""; + } + + @Override + public String getDataBindDefaultValue(CalculatorProvider calculatorProvider) { + if (this.widgetValue == null) { + this.setWidgetValue(new WidgetValue()); + } + + Object result = this.getWidgetValue().executeResult(calculatorProvider); + if (result == null) { + return null; + } else { + return Utils.objectToString(result); + } + } + + @Override + public String getXType() { + return Constants.WIDGET_TYPE; + } + + @Override + public String[] dependence(CalculatorProvider calculatorProvider) { + return super.dependence(calculatorProvider); + } + + @Override + public JSONObject createJSONConfig(Repository repository, Calculator calculator, NodeVisitor nodeVisitor) throws JSONException { + JSONObject jsonConfig = super.createJSONConfig(repository, calculator, nodeVisitor); + Object value = jsonConfig.get("value"); + if (value != null) { + JSONArray jsonVal = (JSONArray) value; + String html = buildSlidesHtml((List) jsonVal.getList()); + jsonConfig.put("value", html); + } + jsonConfig.put(BACKGROUND_COLOR, colorValueToHexString(getBackgroundColor())); + jsonConfig.put(TOOLBAR, isToolBar()); + jsonConfig.put(PROGRESSBAR, isProgressBar()); + jsonConfig.put(PROGRESS, isProgress()); + return jsonConfig; + } + + /** + * 对幻灯片内容和样式进行组合 + */ + private String buildSlidesHtml(List contents) { + SlideStyle slideStyle = StyleFactory.createStyle(getSlideStyle()); + if (slideStyle != null) { + return slideStyle.buildHtml(this, contents); + } + return null; + } + + @Override + public void readXML(XMLableReader reader) { + super.readXML(reader); + if (reader.isChildNode()) { + String tag = reader.getTagName(); + if (tag.equals("EasySlidesAttr")) { + String s = null; + if ((s = reader.getAttrAsString(SLIDE_STYLE, null)) != null) { + setSlideStyle(s); + } + int color; + if ((color = reader.getAttrAsInt(BACKGROUND_COLOR, -1)) != -1) { + setBackgroundColor(new Color(color)); + } + setAutoPlayIntervalTime(reader.getAttrAsInt(AUTO_PLAY_INTERVAL_TIME, 0)); + setToolBar(reader.getAttrAsBoolean(TOOLBAR, false)); + setProgressBar(reader.getAttrAsBoolean(PROGRESSBAR, false)); + setProgress(reader.getAttrAsBoolean(PROGRESS, false)); + } else if ("widgetValue".equals(tag)) { + this.widgetValue = new WidgetValue(); + reader.readXMLObject(this.widgetValue); + } + } + } + + @Override + public void writeXML(XMLPrintWriter writer) { + super.writeXML(writer); + writer.startTAG("EasySlidesAttr"); + writer.attr(SLIDE_STYLE, getSlideStyle()); + writer.attr(BACKGROUND_COLOR, ((Color) getBackgroundColor()).getRGB()); + writer.attr(AUTO_PLAY_INTERVAL_TIME, getAutoPlayIntervalTime()); + writer.attr(TOOLBAR, isToolBar()); + writer.attr(PROGRESSBAR, isProgressBar()); + writer.attr(PROGRESS, isProgress()); + writer.end(); + if (this.widgetValue != null) { + this.widgetValue.writeXML(writer); + } + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/JsFileLoader.java b/src/main/java/com/fr/plugin/easyslides/JsFileLoader.java new file mode 100644 index 0000000..13d1870 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/JsFileLoader.java @@ -0,0 +1,22 @@ +package com.fr.plugin.easyslides; + +import com.fr.plugin.PluginLicense; +import com.fr.plugin.PluginLicenseManager; +import com.fr.stable.fun.Authorize; +import com.fr.stable.fun.impl.AbstractJavaScriptFileHandler; + +@Authorize( + callSignKey = Constants.PLUGIN_ID +) +public class JsFileLoader extends AbstractJavaScriptFileHandler { + private PluginLicense license = PluginLicenseManager.getInstance().getPluginLicenseByID(Constants.PLUGIN_ID); + + @Override + public String[] pathsForFiles() { + return this.license.isAvailable() ? + new String[]{ + "/com/fr/plugin/easyslides/web/widget.js" + } + : null; + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/LocaleFinder.java b/src/main/java/com/fr/plugin/easyslides/LocaleFinder.java new file mode 100644 index 0000000..ce6642c --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/LocaleFinder.java @@ -0,0 +1,10 @@ +package com.fr.plugin.easyslides; + +import com.fr.stable.fun.impl.AbstractLocaleFinder; + +public class LocaleFinder extends AbstractLocaleFinder { + @Override + public String find() { + return "com/fr/plugin/easyslides/local/easyslides"; + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/OptionProvider.java b/src/main/java/com/fr/plugin/easyslides/OptionProvider.java new file mode 100644 index 0000000..34bbd31 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/OptionProvider.java @@ -0,0 +1,34 @@ +package com.fr.plugin.easyslides; + +import com.fr.design.fun.impl.AbstractFormWidgetOptionProvider; +import com.fr.form.ui.Widget; +import com.fr.general.Inter; +import com.fr.plugin.easyslides.ui.EasySlidesWidgetUI; +import com.fr.plugin.transform.ExecuteFunctionRecord; +import com.fr.plugin.transform.FunctionRecorder; + +@FunctionRecorder +public class OptionProvider extends AbstractFormWidgetOptionProvider { + + @ExecuteFunctionRecord + public Class classForWidget() { + return EasySlidesWidget.class; + } + + public Class appearanceForWidget() { + return EasySlidesWidgetUI.class; + } + + public String iconPathForWidget() {//图标 + return "com/fr/plugin/easyslides/images/icon.png"; + } + + public String nameForWidget() {//插件名称 + return Inter.getLocText("Plugin-EasySlides_Widget"); + } + + @Override + public boolean isContainer() {//是否容器 + return false; + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/Util.java b/src/main/java/com/fr/plugin/easyslides/Util.java new file mode 100644 index 0000000..a3cb8f2 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/Util.java @@ -0,0 +1,26 @@ +package com.fr.plugin.easyslides; + +import java.awt.*; + +public class Util { + private Util() { + } + + public static String colorValueToHexString(int color) { + return '#' + Integer.toHexString(color).substring(2); + } + + public static String colorValueToHexString(Object colorObj) { + Color c = (Color) colorObj; + String r = Integer.toHexString(c.getRGB()); + return '#' + r.substring(2); + } + + public static String colorValueToHexString(Object colorObj, float alpha) { + Color color = (Color) colorObj; + Color c = new Color(((float) color.getRed()) / 255, ((float) color.getGreen()) / 255, ((float) color.getBlue()) / 255, alpha); + String r = Integer.toHexString(c.getRGB()); + return '#' + r.substring(2) + r.substring(0, 2); + } + +} diff --git a/src/main/java/com/fr/plugin/easyslides/images/display.png b/src/main/java/com/fr/plugin/easyslides/images/display.png new file mode 100644 index 0000000..fe809b2 Binary files /dev/null and b/src/main/java/com/fr/plugin/easyslides/images/display.png differ diff --git a/src/main/java/com/fr/plugin/easyslides/images/icon.png b/src/main/java/com/fr/plugin/easyslides/images/icon.png new file mode 100644 index 0000000..dae48c2 Binary files /dev/null and b/src/main/java/com/fr/plugin/easyslides/images/icon.png differ diff --git a/src/main/java/com/fr/plugin/easyslides/local/easyslides.properties b/src/main/java/com/fr/plugin/easyslides/local/easyslides.properties new file mode 100644 index 0000000..74690cd --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/local/easyslides.properties @@ -0,0 +1,13 @@ +Plugin-EasySlides_Widget=EasySlides +Plugin-EasySlides_Advanced=Advanced +Plugin-EasySlides_DataSource=DataSource +Plugin-EasySlides_ColorBackground=BackgroundColor +Plugin-EasySlides_AutoPlay=AutoPlay +Plugin-EasySlides_AutoPlayTime=APInterTime +Plugin-EasySlides_SlideStyle=SlideStyle +Plugin-EasySlides_ToolBar=ToolBar +Plugin-EasySlides_ProgressBar=ProgressBar +Plugin-EasySlides_Progress=Progress +Plugin-EasySlides_Style1=horizontal slide +Plugin-EasySlides_Style2=vertical slide +Plugin-EasySlides_Style3=far away slide diff --git a/src/main/java/com/fr/plugin/easyslides/local/easyslides_en_US.properties b/src/main/java/com/fr/plugin/easyslides/local/easyslides_en_US.properties new file mode 100644 index 0000000..2de9dca --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/local/easyslides_en_US.properties @@ -0,0 +1,13 @@ +Plugin-EasySlides_Widget=EasySlides +Plugin-EasySlides_Advanced=Advanced +Plugin-EasySlides_DataSource=DataSource +Plugin-EasySlides_ColorBackground=BackgroundColor +Plugin-EasySlides_AutoPlay=AutoPlay +Plugin-EasySlides_AutoPlayTime=APInterTime +Plugin-EasySlides_SlideStyle=SlideStyle +Plugin-EasySlides_ToolBar=ToolBar +Plugin-EasySlides_ProgressBar=ProgressBar +Plugin-EasySlides_Progress=Progress +Plugin-EasySlides_Style1=horizontal slide +Plugin-EasySlides_Style2=vertical slide +Plugin-EasySlides_Style3=far away slide \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/easyslides/local/easyslides_zh_CN.properties b/src/main/java/com/fr/plugin/easyslides/local/easyslides_zh_CN.properties new file mode 100644 index 0000000..2e752c2 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/local/easyslides_zh_CN.properties @@ -0,0 +1,13 @@ +Plugin-EasySlides_Widget=\u8D85\u9177\u5E7B\u706F\u7247 +Plugin-EasySlides_Advanced=\u9AD8\u7EA7 +Plugin-EasySlides_DataSource=\u6570\u636E\u6E90 +Plugin-EasySlides_ColorBackground=\u80CC\u666F\u8272 +Plugin-EasySlides_AutoPlay=\u81EA\u52A8\u64AD\u653E +Plugin-EasySlides_AutoPlayTime=\u64AD\u653E\u95F4\u9694\u65F6\u95F4 +Plugin-EasySlides_SlideStyle=\u5E7B\u706F\u7247\u6837\u5F0F +Plugin-EasySlides_ToolBar=\u5DE5\u5177\u6761 +Plugin-EasySlides_ProgressBar=\u8FDB\u5EA6\u6761 +Plugin-EasySlides_Progress=\u8FDB\u5EA6 +Plugin-EasySlides_Style1=\u6C34\u5E73\u79FB\u52A8 +Plugin-EasySlides_Style2=\u4E0A\u4E0B\u79FB\u52A8 +Plugin-EasySlides_Style3=\u8FDC\u8FD1\u79FB\u52A8 \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/easyslides/slidestyles/FarAwayStyle.java b/src/main/java/com/fr/plugin/easyslides/slidestyles/FarAwayStyle.java new file mode 100644 index 0000000..6af2422 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/slidestyles/FarAwayStyle.java @@ -0,0 +1,21 @@ +package com.fr.plugin.easyslides.slidestyles; + +import java.util.List; + +/** + * 远近移动 + */ +public class FarAwayStyle extends SlideStyle { + @Override + protected String buildContents(List contents) { + StringBuilder slides = new StringBuilder(); + String divStr = "
\n%s\n
"; + for (int i = 0; i < contents.size(); i++) { + String id = "slide" + i; + String dataZ = String.valueOf(-i * 1500); + slides.append(String.format(divStr, id, dataZ, contents.get(i))); + slides.append('\n'); + } + return slides.toString(); + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/slidestyles/HorizontalStyle.java b/src/main/java/com/fr/plugin/easyslides/slidestyles/HorizontalStyle.java new file mode 100644 index 0000000..34f590b --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/slidestyles/HorizontalStyle.java @@ -0,0 +1,21 @@ +package com.fr.plugin.easyslides.slidestyles; + +import java.util.List; + +/** + * 垂直移动 + */ +public class HorizontalStyle extends SlideStyle { + @Override + protected String buildContents(List contents) { + StringBuilder slides = new StringBuilder(); + String divStr = "
\n%s\n
"; + for (int i = 0; i < contents.size(); i++) { + String id = "slide" + i; + String dataY = String.valueOf(i * 900); + slides.append(String.format(divStr, id, dataY, contents.get(i))); + slides.append('\n'); + } + return slides.toString(); + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/slidestyles/SlideStyle.java b/src/main/java/com/fr/plugin/easyslides/slidestyles/SlideStyle.java new file mode 100644 index 0000000..9078801 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/slidestyles/SlideStyle.java @@ -0,0 +1,51 @@ +package com.fr.plugin.easyslides.slidestyles; + +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.easyslides.EasySlidesWidget; + +import java.util.List; + +public abstract class SlideStyle { + + public final String buildHtml(final EasySlidesWidget widget, List contents) { + if (contents == null || contents.size() <= 0) { + FineLoggerFactory.getLogger().info("There are no any texts in your dataset!"); + return null; + } + + StringBuilder impressStr = new StringBuilder("
\n%s\n
"); + String scriptStr = ""; + + if (widget.isAutoPlay() && widget.getAutoPlayIntervalTime() > 0) { + String autoplayAttr = String.format("data-autoplay=%s", String.valueOf(widget.getAutoPlayIntervalTime())); + impressStr.insert(impressStr.indexOf(">"), " " + autoplayAttr); + } + + StringBuilder plugins = new StringBuilder(); + if (widget.isToolBar()) { + plugins.append("impress_toolbar(document);").append('\n'); + } + + if (widget.isProgressBar() || widget.isProgress()) { + plugins.append("impress_progress(document);").append('\n'); + } + + if (plugins.length() > 0) { + scriptStr = ""; + } + + String contentsHtml = buildContents(contents); + return (String.format(impressStr.toString(), contentsHtml) + scriptStr); + } + + protected abstract String buildContents(List contents); + + /* public static void main(String[] param) { + String impressStr = "
\n%s\n"; + String autoplay = String.format("data-autoplay=%s", String.valueOf(5)); + StringBuilder sb = new StringBuilder(impressStr); + String r = sb.insert(sb.indexOf(">"), " " + autoplay).toString(); + System.out.println(r); + } +*/ +} diff --git a/src/main/java/com/fr/plugin/easyslides/slidestyles/StyleFactory.java b/src/main/java/com/fr/plugin/easyslides/slidestyles/StyleFactory.java new file mode 100644 index 0000000..44a0536 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/slidestyles/StyleFactory.java @@ -0,0 +1,16 @@ +package com.fr.plugin.easyslides.slidestyles; + +import com.fr.general.Inter; + +public class StyleFactory { + public static SlideStyle createStyle(String type) { + if (Inter.getLocText("Plugin-EasySlides_Style1").equalsIgnoreCase(type)) { + return new VerticalStyle(); + } else if (Inter.getLocText("Plugin-EasySlides_Style2").equalsIgnoreCase(type)) { + return new HorizontalStyle(); + } else if (Inter.getLocText("Plugin-EasySlides_Style3").equalsIgnoreCase(type)) { + return new FarAwayStyle(); + } + return null; + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/slidestyles/VerticalStyle.java b/src/main/java/com/fr/plugin/easyslides/slidestyles/VerticalStyle.java new file mode 100644 index 0000000..b72b555 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/slidestyles/VerticalStyle.java @@ -0,0 +1,18 @@ +package com.fr.plugin.easyslides.slidestyles; + +import java.util.List; + +public class VerticalStyle extends SlideStyle { + @Override + protected String buildContents(List contents) { + StringBuilder slides = new StringBuilder(); + String divStr = "
\n%s\n
"; + for (int i = 0; i < contents.size(); i++) { + String id = "slide" + i; + String dataX = String.valueOf(i * 1000); + slides.append(String.format(divStr, id, dataX, contents.get(i))); + slides.append('\n'); + } + return slides.toString(); + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/ui/AutoPlayEditor.java b/src/main/java/com/fr/plugin/easyslides/ui/AutoPlayEditor.java new file mode 100644 index 0000000..a3cf5a1 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/ui/AutoPlayEditor.java @@ -0,0 +1,28 @@ +package com.fr.plugin.easyslides.ui; + +import com.fr.design.Exception.ValidationException; +import com.fr.design.mainframe.widget.editors.AbstractPropertyEditor; + +import java.awt.*; + +public class AutoPlayEditor extends AbstractPropertyEditor { + @Override + public void validateValue() throws ValidationException { + + } + + @Override + public void setValue(Object value) { + + } + + @Override + public Object getValue() { + return null; + } + + @Override + public Component getCustomEditor() { + return null; + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/ui/EasySlidesWidgetUI.java b/src/main/java/com/fr/plugin/easyslides/ui/EasySlidesWidgetUI.java new file mode 100644 index 0000000..1cb0e9e --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/ui/EasySlidesWidgetUI.java @@ -0,0 +1,87 @@ +package com.fr.plugin.easyslides.ui; + +import com.fr.design.designer.creator.CRPropertyDescriptor; +import com.fr.design.designer.creator.XWidgetCreator; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.gui.itextfield.UITextField; +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.mainframe.widget.editors.*; +import com.fr.form.ui.Widget; +import com.fr.general.IOUtils; +import com.fr.general.Inter; +import com.fr.plugin.easyslides.ui.SlidesDataSourceEditor; +import com.fr.stable.ArrayUtils; + +import javax.swing.*; +import java.awt.*; +import java.beans.IntrospectionException; + +import static com.fr.plugin.easyslides.EasySlidesWidget.*; + +public class EasySlidesWidgetUI extends XWidgetCreator { + private UITextField textField; + + public EasySlidesWidgetUI(Widget widget, Dimension dimension) { + super(widget, dimension); + } + + @Override + protected JComponent initEditor() { + if (this.editor == null) { + this.editor = FRGUIPaneFactory.createBorderLayout_S_Pane(); + UILabel label = new UILabel(); + label.setIcon(IOUtils.readIcon("/com/fr/plugin/easyslides/images/display.png")); + label.setHorizontalAlignment(0); + label.setVerticalAlignment(0); + this.editor.add(label, "Center"); + this.textField = new UITextField(5); + this.textField.setOpaque(false); + this.editor.add(this.textField, "South"); + this.editor.setBackground(Color.WHITE); + } + return this.editor; + } + + @Override + public String getIconPath() { + return "/com/fr/plugin/easyslides/images/icon.png"; + } + + public CRPropertyDescriptor[] supportedDescriptor() throws IntrospectionException { + String advanced = Inter.getLocText("Plugin-EasySlides_Advanced"); + return ArrayUtils.addAll(super.supportedDescriptor(), + (new CRPropertyDescriptor("widgetValue", this.data.getClass())) + .setI18NName(Inter.getLocText("Plugin-EasySlides_DataSource")) + .setEditorClass(SlidesDataSourceEditor.class) + .putKeyValue("category", advanced), + (new CRPropertyDescriptor(SLIDE_STYLE, this.data.getClass())) + .setI18NName(Inter.getLocText("Plugin-EasySlides_SlideStyle")) + .setEditorClass(SlideStyleEditor.class) + .putKeyValue("category", advanced), + (new CRPropertyDescriptor(BACKGROUND_COLOR, this.data.getClass())) + .setI18NName(Inter.getLocText("Plugin-EasySlides_ColorBackground")) + .setEditorClass(ColorEditor.class) + .putKeyValue("category", advanced), + (new CRPropertyDescriptor(AUTO_PLAY, this.data.getClass())) + .setI18NName(Inter.getLocText("Plugin-EasySlides_AutoPlay")) + .setEditorClass(BooleanEditor.class) + .putKeyValue("category", advanced), + (new CRPropertyDescriptor(AUTO_PLAY_INTERVAL_TIME, this.data.getClass())) + .setI18NName(Inter.getLocText("Plugin-EasySlides_AutoPlayTime")) + .setEditorClass(IntegerPropertyEditor.class) + .putKeyValue("category", advanced), + (new CRPropertyDescriptor(TOOLBAR, this.data.getClass())) + .setI18NName(Inter.getLocText("Plugin-EasySlides_ToolBar")) + .setEditorClass(BooleanEditor.class) + .putKeyValue("category", advanced), + (new CRPropertyDescriptor(PROGRESSBAR, this.data.getClass())) + .setI18NName(Inter.getLocText("Plugin-EasySlides_ProgressBar")) + .setEditorClass(BooleanEditor.class) + .putKeyValue("category", advanced), + (new CRPropertyDescriptor(PROGRESS, this.data.getClass())) + .setI18NName(Inter.getLocText("Plugin-EasySlides_Progress")) + .setEditorClass(BooleanEditor.class) + .putKeyValue("category", advanced) + ); + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/ui/SlideStyleEditor.java b/src/main/java/com/fr/plugin/easyslides/ui/SlideStyleEditor.java new file mode 100644 index 0000000..c2fe7af --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/ui/SlideStyleEditor.java @@ -0,0 +1,23 @@ +package com.fr.plugin.easyslides.ui; + +import com.fr.design.mainframe.widget.editors.ComboEditor; +import com.fr.general.Inter; + +import javax.swing.*; +import java.util.Vector; + +public class SlideStyleEditor extends ComboEditor { + public static final String DEFAULT_STYLE = Inter.getLocText("Plugin-EasySlides_Style1"); + + public SlideStyleEditor() { + } + + @Override + public ComboBoxModel model() { + Vector vector = new Vector(); + for (int i = 0; i < 3; i++) { + vector.add(Inter.getLocText("Plugin-EasySlides_Style" + (i + 1))); + } + return new DefaultComboBoxModel(vector); + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/ui/SlidesDataSourceEditor.java b/src/main/java/com/fr/plugin/easyslides/ui/SlidesDataSourceEditor.java new file mode 100644 index 0000000..fa1572c --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/ui/SlidesDataSourceEditor.java @@ -0,0 +1,50 @@ +package com.fr.plugin.easyslides.ui; + +import com.fr.design.Exception.ValidationException; +import com.fr.design.editor.ValueEditorPane; +import com.fr.design.editor.editor.Editor; +import com.fr.design.mainframe.widget.editors.AbstractPropertyEditor; +import com.fr.design.mainframe.widget.editors.DataBindingEditor; +import com.fr.form.ui.WidgetValue; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.Component; + +public class SlidesDataSourceEditor extends AbstractPropertyEditor { + + private ValueEditorPane pane; + + public SlidesDataSourceEditor() { + Editor editor = new DataBindingEditor(); + editor.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + SlidesDataSourceEditor.this.firePropertyChanged(); + } + }); + pane = new ValueEditorPane(new Editor[]{editor}); + } + + @Override + public void validateValue() throws ValidationException { + + } + + @Override + public void setValue(Object value) { + if (value != null) { + pane.populate(((WidgetValue) value).getValue()); + } + } + + @Override + public Object getValue() { + return new WidgetValue(pane.update()); + } + + @Override + public Component getCustomEditor() { + return pane; + } +} diff --git a/src/main/java/com/fr/plugin/easyslides/web/impress-default.css b/src/main/java/com/fr/plugin/easyslides/web/impress-default.css new file mode 100644 index 0000000..64369a0 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/web/impress-default.css @@ -0,0 +1,810 @@ +/* + So you like the style of impress.js demo? + Or maybe you are just curious how it was done? + + You couldn't find a better place to find out! + + Welcome to the stylesheet impress.js demo presentation. + + Please remember that it is not meant to be a part of impress.js and is + not required by impress.js. + I expect that anyone creating a presentation for impress.js would create + their own set of styles. + + But feel free to read through it and learn how to get the most of what + impress.js provides. + + And let me be your guide. + + Shall we begin? +*/ + + +/* + We start with a good ol' reset. + That's the one by Eric Meyer http://meyerweb.com/eric/tools/css/reset/ + + You can probably argue if it is needed here, or not, but for sure it + doesn't do any harm and gives us a fresh start. +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* + Now here is when interesting things start to appear. + + We set up styles with default font and nice gradient in the background. + And yes, there is a lot of repetition there because of -prefixes but we don't + want to leave anybody behind. +*/ +/*body { + font-family: 'PT Sans', sans-serif; + min-height: 740px; + + background: rgb(215, 215, 215); + background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 500, from(rgb(240, 240, 240)), to(rgb(190, 190, 190))); + background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); + background: -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); + background: -ms-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); + background: -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); + background: radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); +}*/ + +.slide-body { + font-family: 'PT Sans', sans-serif; + min-height: 740px; + + background: rgb(215, 215, 215); + background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 500, from(rgb(240, 240, 240)), to(rgb(190, 190, 190))); + background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); + background: -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); + background: -ms-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); + background: -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); + background: radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); +} + +/* + Now let's bring some text styles back ... +*/ +b, strong { font-weight: bold } +i, em { font-style: italic } + +/* + ... and give links a nice look. +*/ +a { + color: inherit; + text-decoration: none; + padding: 0 0.1em; + background: rgba(255,255,255,0.5); + text-shadow: -1px -1px 2px rgba(100,100,100,0.9); + border-radius: 0.2em; + + -webkit-transition: 0.5s; + -moz-transition: 0.5s; + -ms-transition: 0.5s; + -o-transition: 0.5s; + transition: 0.5s; +} + +a:hover, +a:focus { + background: rgba(255,255,255,1); + text-shadow: -1px -1px 2px rgba(100,100,100,0.5); +} + +/* + Because the main point behind the impress.js demo is to demo impress.js + we display a fallback message for users with browsers that don't support + all the features required by it. + + All of the content will be still fully accessible for them, but I want + them to know that they are missing something - that's what the demo is + about, isn't it? + + And then we hide the message, when support is detected in the browser. +*/ + +.fallback-message { + font-family: sans-serif; + line-height: 1.3; + + width: 780px; + padding: 10px 10px 0; + margin: 20px auto; + + border: 1px solid #E4C652; + border-radius: 10px; + background: #EEDC94; +} + +.fallback-message p { + margin-bottom: 10px; +} + +.impress-supported .fallback-message { + display: none; +} + +/* + Now let's style the presentation steps. + + We start with basics to make sure it displays correctly in everywhere ... +*/ + +.step { + position: relative; + width: 900px; + padding: 40px; + margin: 20px auto; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; + + font-family: 'PT Serif', georgia, serif; + font-size: 48px; + line-height: 1.5; +} + +/* + ... and we enhance the styles for impress.js. + + Basically we remove the margin and make inactive steps a little bit transparent. +*/ +.impress-enabled .step { + margin: 0; + opacity: 0.3; + + -webkit-transition: opacity 1s; + -moz-transition: opacity 1s; + -ms-transition: opacity 1s; + -o-transition: opacity 1s; + transition: opacity 1s; +} + +.impress-enabled .step.active { opacity: 1 } + +/* + These 'slide' step styles were heavily inspired by HTML5 Slides: + http://html5slides.googlecode.com/svn/trunk/styles.css + + ;) + + They cover everything what you see on first three steps of the demo. +*/ +.slide { + display: block; + + width: 900px; + height: 700px; + padding: 40px 60px; + + background-color: white; + border: 1px solid rgba(0, 0, 0, .3); + border-radius: 10px; + box-shadow: 0 2px 6px rgba(0, 0, 0, .1); + + color: rgb(102, 102, 102); + text-shadow: 0 2px 2px rgba(0, 0, 0, .1); + + font-family: 'Open Sans', Arial, sans-serif; + font-size: 30px; + line-height: 36px; + letter-spacing: -1px; +} + +.slide q { + display: block; + font-size: 50px; + line-height: 72px; + + margin-top: 100px; +} + +.slide q strong { + white-space: nowrap; +} + +/* + And now we start to style each step separately. + + I agree that this may be not the most efficient, object-oriented and + scalable way of styling, but most of steps have quite a custom look + and typography tricks here and there, so they had to be styles separately. + + First is the title step with a big

(no room for padding) and some + 3D positioning along Z axis. +*/ + +#title { + padding: 0; +} + +#title .try { + font-size: 64px; + position: absolute; + top: -0.5em; + left: 1.5em; + + -webkit-transform: translateZ(20px); + -moz-transform: translateZ(20px); + -ms-transform: translateZ(20px); + -o-transform: translateZ(20px); + transform: translateZ(20px); +} + +#title h1 { + font-size: 190px; + + -webkit-transform: translateZ(50px); + -moz-transform: translateZ(50px); + -ms-transform: translateZ(50px); + -o-transform: translateZ(50px); + transform: translateZ(50px); +} + +#title .footnote { + font-size: 32px; +} + +/* + Second step is nothing special, just a text with a link, so it doesn't need + any special styling. + + Let's move to 'big thoughts' with centered text and custom font sizes. +*/ +#big { + width: 600px; + text-align: center; + font-size: 60px; + line-height: 1; +} + +#big b { + display: block; + font-size: 250px; + line-height: 250px; +} + +#big .thoughts { + font-size: 90px; + line-height: 150px; +} + +/* + 'Tiny ideas' just need some tiny styling. +*/ +#tiny { + width: 500px; + text-align: center; +} + +/* + This step has some animated text ... +*/ +#ing { width: 500px } + +/* + ... so we define display to `inline-block` to enable transforms and + transition duration to 0.5s ... +*/ +#ing b { + display: inline-block; + -webkit-transition: 0.5s; + -moz-transition: 0.5s; + -ms-transition: 0.5s; + -o-transition: 0.5s; + transition: 0.5s; +} + +/* + ... and we want 'positioning` word to move up a bit when the step gets + `present` class ... +*/ +#ing.present .positioning { + -webkit-transform: translateY(-10px); + -moz-transform: translateY(-10px); + -ms-transform: translateY(-10px); + -o-transform: translateY(-10px); + transform: translateY(-10px); +} + +/* + ... 'rotating' to rotate quater of a second later ... +*/ +#ing.present .rotating { + -webkit-transform: rotate(-10deg); + -moz-transform: rotate(-10deg); + -ms-transform: rotate(-10deg); + -o-transform: rotate(-10deg); + transform: rotate(-10deg); + + -webkit-transition-delay: 0.25s; + -moz-transition-delay: 0.25s; + -ms-transition-delay: 0.25s; + -o-transition-delay: 0.25s; + transition-delay: 0.25s; +} + +/* + ... and 'scaling' to scale down after another quater of a second. +*/ +#ing.present .scaling { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -ms-transform: scale(0.7); + -o-transform: scale(0.7); + transform: scale(0.7); + + -webkit-transition-delay: 0.5s; + -moz-transition-delay: 0.5s; + -ms-transition-delay: 0.5s; + -o-transition-delay: 0.5s; + transition-delay: 0.5s; +} + +/* + The 'imagination' step is again some boring font-sizing. +*/ + +#imagination { + width: 600px; +} + +#imagination .imagination { + font-size: 78px; +} + +/* + There is nothing really special about 'use the source, Luke' step, too, + except maybe of the Yoda background. + + As you can see below I've 'hard-coded' it in data URL. + That's not the best way to serve images, but because that's just this one + I decided it will be OK to have it this way. + + Just make sure you don't blindly copy this approach. +*/ +#source { + width: 700px; + padding-bottom: 300px; + + /* Yoda Icon :: Pixel Art from Star Wars http://www.pixeljoint.com/pixelart/1423.htm */ + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAEYCAMAAACwUBm+AAAAAXNSR0IArs4c6QAAAKtQTFRFsAAAvbWSLUUrLEQqY1s8UYJMqJ1vNTEgOiIdIzYhjIFVLhsXZ6lgSEIsP2U8JhcCVzMsSXZEgXdOO145XJdWOl03LzAYMk4vSXNExr+hwcuxRTs1Qmk+RW9Am49eFRANQz4pUoNMQWc+OSMDTz0wLBsCNVMxa2NBOyUDUoNNSnlEWo9VRGxAVzYFl6tXCggHbLNmMUIcHhwTXkk5f3VNRT8wUT8xAAAACQocRBWFFwAAAAF0Uk5TAEDm2GYAAAPCSURBVHja7d3JctNAFIZRMwRCCGEmzPM8z/D+T8bu/ptbXXJFdij5fMt2Wuo+2UgqxVmtttq5WVotLzBgwIABAwYMGDCn0qVqbo69psPqVpWx+1XG5iaavF8wYMCAAQMGDBgwi4DJ6Y6qkxB1HNlcN3a92gbR5P2CAQMGDBgwYMCAWSxMlrU+UY5yu2l9okfV4bAxUVbf7TJnAwMGDBgwYMCAAbMLMHeqbGR82Zy+VR1Ht81nVca6R+UdTLaU24Ruzd3qM/e4yjnAgAEDBgwYMGDA7AJMd1l/3NRdVGcj3eX/2WEhCmDGxnM7yqygu8XIPjJj8iN/MGDAgAEDBgwYMAuDGb8q0RGlLCHLv1t9qDKWn3vdNHVuEI6HPaxO9Jo3GDBgwIABAwYMmIXBdC9ShGgMk+XnkXUeuGcsP/e1+lhNnZsL/G5Vs3OAAQMGDBgwYMCAWSxMR3SzOmraG5atdy9wZKzb+vg16qyqe2FltbnAgAEDBgwYMGDALAxmTJSuN3WA76rnVca6GTnemGN1WoEBAwYMGDBgwIBZGMxUomy4+xO899V4LAg5Xnc2MGDAgAEDBgwYMGA218Wq+2K1LDqvY9xZu8zN8fICdM6btYABAwYMGDBgwIABMzfH0+pGU5afze2tXebmeAfVz+p8BQYMGDBgwIABAwbMPBzZ+oWmfJrln1273FhkbHzee9WWbw7AgAEDBgwYMGDALAKm43hcdctKgblcPamOhuXnXlY5Xs6bsW4FGyQCAwYMGDBgwIABswiYMceZKgvMo+h8mrHLTdn676rj+FEFoTtHd8MwOxEYMGDAgAEDBgyYRcBM5UhXqiymW3R3c9ARhWO/OmjqfjVZy+xEYMCAAQMGDBgwYBYG073OnCV0RFNhMhaOa9WfKmOB6XjHMN1tQmaAAQMGDBgwYMCA2VWY7vXjz1U4croAzgPztwIDBgwYMGDAgAEDZhswh035NBw59Dww3RgYMGDAgAEDBgwYMJuD6f4tXT7NUqfCdBvZLkxXdgQGDBgwYMCAAQNmt2DGj8WzwAfV/w7T/aq7mxwwYMCAAQMGDBgwuwqTOo7uTwTngflSzQ3TdaJvAwEDBgwYMGDAgAED5gSvgbyo5oHZ4Pc+gwEDBgwYMGDAgAEzhOm+5G0qTGaAAQMGDBgwYMCAAXNaMOcnls3tNwWm+zRzp54NDBgwYMCAAQMGDJh5YNL36k1TLuGvVq+qnKMbS5n7tulT9asCAwYMGDBgwIABA2ZumKuztLnjgQEDBgwYMGDAgNl5mH/4/ltKA6vBNAAAAABJRU5ErkJggg==); + background-position: bottom right; + background-repeat: no-repeat; +} + +#source q { + font-size: 60px; +} + +/* + And the "it's in 3D" step again brings some 3D typography - just for fun. + + Because we want to position elements in 3D we set transform-style to + `preserve-3d` on the paragraph. + It is not needed by webkit browsers, but it is in Firefox. It's hard to say + which behaviour is correct as 3D transforms spec is not very clear about it. +*/ +#its-in-3d p { + -webkit-transform-style: preserve-3d; + -moz-transform-style: preserve-3d; /* Y U need this Firefox?! */ + -ms-transform-style: preserve-3d; + -o-transform-style: preserve-3d; + transform-style: preserve-3d; +} + +/* + Below we position each word separately along Z axis and we want it to transition + to default position in 0.5s when the step gets `present` class. + + Quite a simple idea, but lot's of styles and prefixes. +*/ +#its-in-3d span, +#its-in-3d b { + display: inline-block; + -webkit-transform: translateZ(40px); + -moz-transform: translateZ(40px); + -ms-transform: translateZ(40px); + -o-transform: translateZ(40px); + transform: translateZ(40px); + + -webkit-transition: 0.5s; + -moz-transition: 0.5s; + -ms-transition: 0.5s; + -o-transition: 0.5s; + transition: 0.5s; +} + +#its-in-3d .have { + -webkit-transform: translateZ(-40px); + -moz-transform: translateZ(-40px); + -ms-transform: translateZ(-40px); + -o-transform: translateZ(-40px); + transform: translateZ(-40px); +} + +#its-in-3d .you { + -webkit-transform: translateZ(20px); + -moz-transform: translateZ(20px); + -ms-transform: translateZ(20px); + -o-transform: translateZ(20px); + transform: translateZ(20px); +} + +#its-in-3d .noticed { + -webkit-transform: translateZ(-40px); + -moz-transform: translateZ(-40px); + -ms-transform: translateZ(-40px); + -o-transform: translateZ(-40px); + transform: translateZ(-40px); +} + +#its-in-3d .its { + -webkit-transform: translateZ(60px); + -moz-transform: translateZ(60px); + -ms-transform: translateZ(60px); + -o-transform: translateZ(60px); + transform: translateZ(60px); +} + +#its-in-3d .in { + -webkit-transform: translateZ(-10px); + -moz-transform: translateZ(-10px); + -ms-transform: translateZ(-10px); + -o-transform: translateZ(-10px); + transform: translateZ(-10px); +} + +#its-in-3d .footnote { + font-size: 32px; + + -webkit-transform: translateZ(-10px); + -moz-transform: translateZ(-10px); + -ms-transform: translateZ(-10px); + -o-transform: translateZ(-10px); + transform: translateZ(-10px); +} + +#its-in-3d.present span, +#its-in-3d.present b { + -webkit-transform: translateZ(0px); + -moz-transform: translateZ(0px); + -ms-transform: translateZ(0px); + -o-transform: translateZ(0px); + transform: translateZ(0px); +} + +/* + The last step is an overview. + There is no content in it, so we make sure it's not visible because we want + to be able to click on other steps. + +*/ +#overview { display: none } + +/* + We also make other steps visible and give them a pointer cursor using the + `impress-on-` class. +*/ +.impress-on-overview .step { + opacity: 1; + cursor: pointer; +} + + +/* + Now, when we have all the steps styled let's give users a hint how to navigate + around the presentation. + + The best way to do this would be to use JavaScript, show a delayed hint for a + first time users, then hide it and store a status in cookie or localStorage... + + But I wanted to have some CSS fun and avoid additional scripting... + + Let me explain it first, so maybe the transition magic will be more readable + when you read the code. + + First of all I wanted the hint to appear only when user is idle for a while. + You can't detect the 'idle' state in CSS, but I delayed a appearing of the + hint by 5s using transition-delay. + + You also can't detect in CSS if the user is a first-time visitor, so I had to + make an assumption that I'll only show the hint on the first step. And when + the step is changed hide the hint, because I can assume that user already + knows how to navigate. + + To summarize it - hint is shown when the user is on the first step for longer + than 5 seconds. + + The other problem I had was caused by the fact that I wanted the hint to fade + in and out. It can be easily achieved by transitioning the opacity property. + But that also meant that the hint was always on the screen, even if totally + transparent. It covered part of the screen and you couldn't correctly clicked + through it. + Unfortunately you cannot transition between display `block` and `none` in pure + CSS, so I needed a way to not only fade out the hint but also move it out of + the screen. + + I solved this problem by positioning the hint below the bottom of the screen + with CSS transform and moving it up to show it. But I also didn't want this move + to be visible. I wanted the hint only to fade in and out visually, so I delayed + the fade in transition, so it starts when the hint is already in its correct + position on the screen. + + I know, it sounds complicated ... maybe it would be easier with the code? +*/ + +.hint { + /* + We hide the hint until presentation is started and from browsers not supporting + impress.js, as they will have a linear scrollable view ... + */ + display: none; + + /* + ... and give it some fixed position and nice styles. + */ + position: fixed; + left: 0; + right: 0; + bottom: 200px; + + background: rgba(0,0,0,0.5); + color: #EEE; + text-align: center; + + font-size: 50px; + padding: 20px; + + z-index: 100; + + /* + By default we don't want the hint to be visible, so we make it transparent ... + */ + opacity: 0; + + /* + ... and position it below the bottom of the screen (relative to it's fixed position) + */ + -webkit-transform: translateY(400px); + -moz-transform: translateY(400px); + -ms-transform: translateY(400px); + -o-transform: translateY(400px); + transform: translateY(400px); + + /* + Now let's imagine that the hint is visible and we want to fade it out and move out + of the screen. + + So we define the transition on the opacity property with 1s duration and another + transition on transform property delayed by 1s so it will happen after the fade out + on opacity finished. + + This way user will not see the hint moving down. + */ + -webkit-transition: opacity 1s, -webkit-transform 0.5s 1s; + -moz-transition: opacity 1s, -moz-transform 0.5s 1s; + -ms-transition: opacity 1s, -ms-transform 0.5s 1s; + -o-transition: opacity 1s, -o-transform 0.5s 1s; + transition: opacity 1s, transform 0.5s 1s; +} + +/* + Now we 'enable' the hint when presentation is initialized ... +*/ +.impress-enabled .hint { display: block } + +/* + ... and we will show it when the first step (with id 'bored') is active. +*/ +.impress-on-bored .hint { + /* + We remove the transparency and position the hint in its default fixed + position. + */ + opacity: 1; + + -webkit-transform: translateY(0px); + -moz-transform: translateY(0px); + -ms-transform: translateY(0px); + -o-transform: translateY(0px); + transform: translateY(0px); + + /* + Now for fade in transition we have the oposite situation from the one + above. + + First after 4.5s delay we animate the transform property to move the hint + into its correct position and after that we fade it in with opacity + transition. + */ + -webkit-transition: opacity 1s 5s, -webkit-transform 0.5s 4.5s; + -moz-transition: opacity 1s 5s, -moz-transform 0.5s 4.5s; + -ms-transition: opacity 1s 5s, -ms-transform 0.5s 4.5s; + -o-transition: opacity 1s 5s, -o-transform 0.5s 4.5s; + transition: opacity 1s 5s, transform 0.5s 4.5s; +} + +/* + And as the last thing there is a workaround for quite strange bug. + It happens a lot in Chrome. I don't remember if I've seen it in Firefox. + + Sometimes the element positioned in 3D (especially when it's moved back + along Z axis) is not clickable, because it falls 'behind' the + element. + + To prevent this, I decided to make non clickable by setting + pointer-events property to `none` value. + Value if this property is inherited, so to make everything else clickable + I bring it back on the #impress element. + + If you want to know more about `pointer-events` here are some docs: + https://developer.mozilla.org/en/CSS/pointer-events + + There is one very important thing to notice about this workaround - it makes + everything 'unclickable' except what's in #impress element. + + So use it wisely ... or don't use at all. +*/ +.impress-enabled { pointer-events: none } +.impress-enabled #impress { pointer-events: auto } + +/* + There is one funny thing I just realized. + + Thanks to this workaround above everything except #impress element is invisible + for click events. That means that the hint element is also not clickable. + So basically all of this transforms and delayed transitions trickery was probably + not needed at all... + + But it was fun to learn about it, wasn't it? +*/ + +/* + That's all I have for you in this file. + Thanks for reading. I hope you enjoyed it at least as much as I enjoyed writing it + for you. +*/ + +/******************* PLUGINS *************************************************************/ +/* + This version of impress.js supports plugins, and in particular, a UI toolbar + plugin that allows easy navigation between steps and autoplay. +*/ +.impress-enabled div#impress-toolbar { + position: fixed; + right: 1px; + bottom: 1px; + opacity: 0.6; + z-index: 10; +} +.impress-enabled div#impress-toolbar > span { + margin-right: 10px; +} + +/* + With help from the mouse-timeout plugin, we can hide the toolbar and + have it show only when you move/click/touch the mouse. +*/ +body.impress-mouse-timeout div#impress-toolbar { + display: none; +} + +/* + In fact, we can hide the mouse cursor itself too, when mouse isn't used. +*/ +body.impress-mouse-timeout { + cursor: none; +} + + + +/* Progress bar */ +.impress-progressbar { + position: absolute; + right: 118px; + bottom: 1px; + left: 118px; + border-radius: 7px; + border: 2px solid rgba(100, 100, 100, 0.2); +} +.impress-progressbar DIV { + width: 0; + height: 2px; + border-radius: 5px; + background: rgba(75, 75, 75, 0.4); + transition: width 1s linear; +} +.impress-progress { + position: absolute; + left: 59px; + bottom: 1px; + text-align: left; + font-size: 10pt; + opacity: 0.6; +} + +/* Help popup plugin */ +.impress-enabled #impress-help { + background: none repeat scroll 0 0 rgba(0, 0, 0, 0.5); + color: #EEEEEE; + font-size: 80%; + position: fixed; + left: 2em; + bottom: 2em; + width: 24em; + border-radius: 1em; + padding: 1em; + text-align: center; + z-index: 100; + font-family: Verdana, Arial, Sans; +} +.impress-enabled #impress-help td { + padding-left: 1em; + padding-right: 1em; +} + +/* Substep plugin */ + +#impress .step .substep { + opacity: 0; +} + +#impress .step .substep.substep-visible { + opacity: 1; + transition: opacity 1s; +} + +.impress-enabled { pointer-events: none } +.impress-enabled #impress { pointer-events: auto } +.impress-enabled #impress-toolbar { pointer-events: auto } +.impress-enabled #impress-console-button { pointer-events: auto } diff --git a/src/main/java/com/fr/plugin/easyslides/web/impress-main.js b/src/main/java/com/fr/plugin/easyslides/web/impress-main.js new file mode 100644 index 0000000..a0c2b7f --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/web/impress-main.js @@ -0,0 +1,918 @@ +// This file was automatically generated from files in src/ directory. + +/*! Licensed under MIT License - http://github.com/impress/impress.js */ +/** + * impress.js + * + * impress.js is a presentation tool based on the power of CSS3 transforms and transitions + * in modern browsers and inspired by the idea behind prezi.com. + * + * + * Copyright 2011-2012 Bartek Szopka (@bartaz), 2016-2018 Henrik Ingo (@henrikingo) + * + * Released under the MIT License. + * + * ------------------------------------------------ + * author: Bartek Szopka, Henrik Ingo + * version: 1.0.0 + * url: http://impress.js.org + * source: http://github.com/impress/impress.js/ + */ + +// You are one of those who like to know how things work inside? +// Let me show you the cogs that make impress.js run... +( function( document, window ) { + "use strict"; + var lib; + + // HELPER FUNCTIONS + + // `pfx` is a function that takes a standard CSS property name as a parameter + // and returns it's prefixed version valid for current browser it runs in. + // The code is heavily inspired by Modernizr http://www.modernizr.com/ + var pfx = ( function() { + + var style = document.createElement( "dummy" ).style, + prefixes = "Webkit Moz O ms Khtml".split( " " ), + memory = {}; + + return function( prop ) { + if ( typeof memory[ prop ] === "undefined" ) { + + var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), + props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " ); + + memory[ prop ] = null; + for ( var i in props ) { + if ( style[ props[ i ] ] !== undefined ) { + memory[ prop ] = props[ i ]; + break; + } + } + + } + + return memory[ prop ]; + }; + + } )(); + + var validateOrder = function( order, fallback ) { + var validChars = "xyz"; + var returnStr = ""; + if ( typeof order === "string" ) { + for ( var i in order.split( "" ) ) { + if ( validChars.indexOf( order[ i ] ) >= 0 ) { + returnStr += order[ i ]; + + // Each of x,y,z can be used only once. + validChars = validChars.split( order[ i ] ).join( "" ); + } + } + } + if ( returnStr ) { + return returnStr; + } else if ( fallback !== undefined ) { + return fallback; + } else { + return "xyz"; + } + }; + + // `css` function applies the styles given in `props` object to the element + // given as `el`. It runs all property names through `pfx` function to make + // sure proper prefixed version of the property is used. + var css = function( el, props ) { + var key, pkey; + for ( key in props ) { + if ( props.hasOwnProperty( key ) ) { + pkey = pfx( key ); + if ( pkey !== null ) { + el.style[ pkey ] = props[ key ]; + } + } + } + return el; + }; + + // `translate` builds a translate transform string for given data. + var translate = function( t ) { + return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) "; + }; + + // `rotate` builds a rotate transform string for given data. + // By default the rotations are in X Y Z order that can be reverted by passing `true` + // as second parameter. + var rotate = function( r, revert ) { + var order = r.order ? r.order : "xyz"; + var css = ""; + var axes = order.split( "" ); + if ( revert ) { + axes = axes.reverse(); + } + + for ( var i = 0; i < axes.length; i++ ) { + css += " rotate" + axes[ i ].toUpperCase() + "(" + r[ axes[ i ] ] + "deg)"; + } + return css; + }; + + // `scale` builds a scale transform string for given data. + var scale = function( s ) { + return " scale(" + s + ") "; + }; + + // `computeWindowScale` counts the scale factor between window size and size + // defined for the presentation in the config. + var computeWindowScale = function( config ) { + var hScale = window.innerHeight / config.height, + wScale = window.innerWidth / config.width, + scale = hScale > wScale ? wScale : hScale; + + if ( config.maxScale && scale > config.maxScale ) { + scale = config.maxScale; + } + + if ( config.minScale && scale < config.minScale ) { + scale = config.minScale; + } + + return scale; + }; + + // CHECK SUPPORT + var body = document.body; + var impressSupported = + + // Browser should support CSS 3D transtorms + ( pfx( "perspective" ) !== null ) && + + // And `classList` and `dataset` APIs + ( body.classList ) && + ( body.dataset ); + + if ( !impressSupported ) { + + // We can't be sure that `classList` is supported + body.className += " impress-not-supported "; + } + + // GLOBALS AND DEFAULTS + + // This is where the root elements of all impress.js instances will be kept. + // Yes, this means you can have more than one instance on a page, but I'm not + // sure if it makes any sense in practice ;) + var roots = {}; + + var preInitPlugins = []; + var preStepLeavePlugins = []; + + // Some default config values. + var defaults = { + width: 1024, + height: 768, + maxScale: 1, + minScale: 0, + + perspective: 1000, + + transitionDuration: 1000 + }; + + // It's just an empty function ... and a useless comment. + var empty = function() { return false; }; + + // IMPRESS.JS API + + // And that's where interesting things will start to happen. + // It's the core `impress` function that returns the impress.js API + // for a presentation based on the element with given id ("impress" + // by default). + var impress = window.impress = function( rootId ) { + + // If impress.js is not supported by the browser return a dummy API + // it may not be a perfect solution but we return early and avoid + // running code that may use features not implemented in the browser. + if ( !impressSupported ) { + return { + init: empty, + goto: empty, + prev: empty, + next: empty, + swipe: empty, + tear: empty, + lib: {} + }; + } + + rootId = rootId || "impress"; + + // If given root is already initialized just return the API + if ( roots[ "impress-root-" + rootId ] ) { + return roots[ "impress-root-" + rootId ]; + } + + // The gc library depends on being initialized before we do any changes to DOM. + lib = initLibraries( rootId ); + + body.classList.remove( "impress-not-supported" ); + body.classList.add( "impress-supported" ); + + // Data of all presentation steps + var stepsData = {}; + + // Element of currently active step + var activeStep = null; + + // Current state (position, rotation and scale) of the presentation + var currentState = null; + + // Array of step elements + var steps = null; + + // Configuration options + var config = null; + + // Scale factor of the browser window + var windowScale = null; + + // Root presentation elements + var root = lib.util.byId( rootId ); + var canvas = document.createElement( "div" ); + + var initialized = false; + + // STEP EVENTS + // + // There are currently two step events triggered by impress.js + // `impress:stepenter` is triggered when the step is shown on the + // screen (the transition from the previous one is finished) and + // `impress:stepleave` is triggered when the step is left (the + // transition to next step just starts). + + // Reference to last entered step + var lastEntered = null; + + // `onStepEnter` is called whenever the step element is entered + // but the event is triggered only if the step is different than + // last entered step. + // We sometimes call `goto`, and therefore `onStepEnter`, just to redraw a step, such as + // after screen resize. In this case - more precisely, in any case - we trigger a + // `impress:steprefresh` event. + var onStepEnter = function( step ) { + if ( lastEntered !== step ) { + lib.util.triggerEvent( step, "impress:stepenter" ); + lastEntered = step; + } + lib.util.triggerEvent( step, "impress:steprefresh" ); + }; + + // `onStepLeave` is called whenever the currentStep element is left + // but the event is triggered only if the currentStep is the same as + // lastEntered step. + var onStepLeave = function( currentStep, nextStep ) { + if ( lastEntered === currentStep ) { + lib.util.triggerEvent( currentStep, "impress:stepleave", { next: nextStep } ); + lastEntered = null; + } + }; + + // `initStep` initializes given step element by reading data from its + // data attributes and setting correct styles. + var initStep = function( el, idx ) { + var data = el.dataset, + step = { + translate: { + x: lib.util.toNumber( data.x ), + y: lib.util.toNumber( data.y ), + z: lib.util.toNumber( data.z ) + }, + rotate: { + x: lib.util.toNumber( data.rotateX ), + y: lib.util.toNumber( data.rotateY ), + z: lib.util.toNumber( data.rotateZ || data.rotate ), + order: validateOrder( data.rotateOrder ) + }, + scale: lib.util.toNumber( data.scale, 1 ), + transitionDuration: lib.util.toNumber( + data.transitionDuration, config.transitionDuration + ), + el: el + }; + + if ( !el.id ) { + el.id = "step-" + ( idx + 1 ); + } + + stepsData[ "impress-" + el.id ] = step; + + css( el, { + position: "absolute", + transform: "translate(-50%,-50%)" + + translate( step.translate ) + + rotate( step.rotate ) + + scale( step.scale ), + transformStyle: "preserve-3d" + } ); + }; + + // Initialize all steps. + // Read the data-* attributes, store in internal stepsData, and render with CSS. + var initAllSteps = function() { + steps = lib.util.$$( ".step", root ); + steps.forEach( initStep ); + }; + + // `init` API function that initializes (and runs) the presentation. + var init = function() { + if ( initialized ) { return; } + execPreInitPlugins( root ); + + // First we set up the viewport for mobile devices. + // For some reason iPad goes nuts when it is not done properly. + var meta = lib.util.$( "meta[name='viewport']" ) || document.createElement( "meta" ); + meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no"; + if ( meta.parentNode !== document.head ) { + meta.name = "viewport"; + document.head.appendChild( meta ); + } + + // Initialize configuration object + var rootData = root.dataset; + config = { + width: lib.util.toNumber( rootData.width, defaults.width ), + height: lib.util.toNumber( rootData.height, defaults.height ), + maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ), + minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ), + perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ), + transitionDuration: lib.util.toNumber( + rootData.transitionDuration, defaults.transitionDuration + ) + }; + + windowScale = computeWindowScale( config ); + + // Wrap steps with "canvas" element + lib.util.arrayify( root.childNodes ).forEach( function( el ) { + canvas.appendChild( el ); + } ); + root.appendChild( canvas ); + + // Set initial styles + document.documentElement.style.height = "100%"; + + css( body, { + height: "100%", + overflow: "hidden" + } ); + + var rootStyles = { + position: "absolute", + transformOrigin: "top left", + transition: "all 0s ease-in-out", + transformStyle: "preserve-3d" + }; + + css( root, rootStyles ); + css( root, { + top: "50%", + left: "50%", + perspective: ( config.perspective / windowScale ) + "px", + transform: scale( windowScale ) + } ); + css( canvas, rootStyles ); + + body.classList.remove( "impress-disabled" ); + body.classList.add( "impress-enabled" ); + + // Get and init steps + initAllSteps(); + + // Set a default initial state of the canvas + currentState = { + translate: { x: 0, y: 0, z: 0 }, + rotate: { x: 0, y: 0, z: 0, order: "xyz" }, + scale: 1 + }; + + initialized = true; + + lib.util.triggerEvent( root, "impress:init", + { api: roots[ "impress-root-" + rootId ] } ); + }; + + // `getStep` is a helper function that returns a step element defined by parameter. + // If a number is given, step with index given by the number is returned, if a string + // is given step element with such id is returned, if DOM element is given it is returned + // if it is a correct step element. + var getStep = function( step ) { + if ( typeof step === "number" ) { + step = step < 0 ? steps[ steps.length + step ] : steps[ step ]; + } else if ( typeof step === "string" ) { + step = lib.util.byId( step ); + } + return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null; + }; + + // Used to reset timeout for `impress:stepenter` event + var stepEnterTimeout = null; + + // `goto` API function that moves to step given as `el` parameter (by index, id or element). + // `duration` optionally given as second parameter, is the transition duration in css. + // `reason` is the string "next", "prev" or "goto" (default) and will be made available to + // preStepLeave plugins. + // `origEvent` may contain event that caused the call to goto, such as a key press event + var goto = function( el, duration, reason, origEvent ) { + reason = reason || "goto"; + origEvent = origEvent || null; + + if ( !initialized ) { + return false; + } + + // Re-execute initAllSteps for each transition. This allows to edit step attributes + // dynamically, such as change their coordinates, or even remove or add steps, and have + // that change apply when goto() is called. + initAllSteps(); + + if ( !( el = getStep( el ) ) ) { + return false; + } + + // Sometimes it's possible to trigger focus on first link with some keyboard action. + // Browser in such a case tries to scroll the page to make this element visible + // (even that body overflow is set to hidden) and it breaks our careful positioning. + // + // So, as a lousy (and lazy) workaround we will make the page scroll back to the top + // whenever slide is selected + // + // If you are reading this and know any better way to handle it, I'll be glad to hear + // about it! + window.scrollTo( 0, 0 ); + + var step = stepsData[ "impress-" + el.id ]; + duration = ( duration !== undefined ? duration : step.transitionDuration ); + + // If we are in fact moving to another step, start with executing the registered + // preStepLeave plugins. + if ( activeStep && activeStep !== el ) { + var event = { target: activeStep, detail: {} }; + event.detail.next = el; + event.detail.transitionDuration = duration; + event.detail.reason = reason; + if ( origEvent ) { + event.origEvent = origEvent; + } + + if ( execPreStepLeavePlugins( event ) === false ) { + + // PreStepLeave plugins are allowed to abort the transition altogether, by + // returning false. + // see stop and substep plugins for an example of doing just that + return false; + } + + // Plugins are allowed to change the detail values + el = event.detail.next; + step = stepsData[ "impress-" + el.id ]; + duration = event.detail.transitionDuration; + } + + if ( activeStep ) { + activeStep.classList.remove( "active" ); + body.classList.remove( "impress-on-" + activeStep.id ); + } + el.classList.add( "active" ); + + body.classList.add( "impress-on-" + el.id ); + + // Compute target state of the canvas based on given step + var target = { + rotate: { + x: -step.rotate.x, + y: -step.rotate.y, + z: -step.rotate.z, + order: step.rotate.order + }, + translate: { + x: -step.translate.x, + y: -step.translate.y, + z: -step.translate.z + }, + scale: 1 / step.scale + }; + + // Check if the transition is zooming in or not. + // + // This information is used to alter the transition style: + // when we are zooming in - we start with move and rotate transition + // and the scaling is delayed, but when we are zooming out we start + // with scaling down and move and rotation are delayed. + var zoomin = target.scale >= currentState.scale; + + duration = lib.util.toNumber( duration, config.transitionDuration ); + var delay = ( duration / 2 ); + + // If the same step is re-selected, force computing window scaling, + // because it is likely to be caused by window resize + if ( el === activeStep ) { + windowScale = computeWindowScale( config ); + } + + var targetScale = target.scale * windowScale; + + // Trigger leave of currently active element (if it's not the same step again) + if ( activeStep && activeStep !== el ) { + onStepLeave( activeStep, el ); + } + + // Now we alter transforms of `root` and `canvas` to trigger transitions. + // + // And here is why there are two elements: `root` and `canvas` - they are + // being animated separately: + // `root` is used for scaling and `canvas` for translate and rotations. + // Transitions on them are triggered with different delays (to make + // visually nice and "natural" looking transitions), so we need to know + // that both of them are finished. + css( root, { + + // To keep the perspective look similar for different scales + // we need to "scale" the perspective, too + // For IE 11 support we must specify perspective independent + // of transform. + perspective: ( config.perspective / targetScale ) + "px", + transform: scale( targetScale ), + transitionDuration: duration + "ms", + transitionDelay: ( zoomin ? delay : 0 ) + "ms" + } ); + + css( canvas, { + transform: rotate( target.rotate, true ) + translate( target.translate ), + transitionDuration: duration + "ms", + transitionDelay: ( zoomin ? 0 : delay ) + "ms" + } ); + + // Here is a tricky part... + // + // If there is no change in scale or no change in rotation and translation, it means + // there was actually no delay - because there was no transition on `root` or `canvas` + // elements. We want to trigger `impress:stepenter` event in the correct moment, so + // here we compare the current and target values to check if delay should be taken into + // account. + // + // I know that this `if` statement looks scary, but it's pretty simple when you know + // what is going on - it's simply comparing all the values. + if ( currentState.scale === target.scale || + ( currentState.rotate.x === target.rotate.x && + currentState.rotate.y === target.rotate.y && + currentState.rotate.z === target.rotate.z && + currentState.translate.x === target.translate.x && + currentState.translate.y === target.translate.y && + currentState.translate.z === target.translate.z ) ) { + delay = 0; + } + + // Store current state + currentState = target; + activeStep = el; + + // And here is where we trigger `impress:stepenter` event. + // We simply set up a timeout to fire it taking transition duration (and possible delay) + // into account. + // + // I really wanted to make it in more elegant way. The `transitionend` event seemed to + // be the best way to do it, but the fact that I'm using transitions on two separate + // elements and that the `transitionend` event is only triggered when there was a + // transition (change in the values) caused some bugs and made the code really + // complicated, cause I had to handle all the conditions separately. And it still + // needed a `setTimeout` fallback for the situations when there is no transition at all. + // So I decided that I'd rather make the code simpler than use shiny new + // `transitionend`. + // + // If you want learn something interesting and see how it was done with `transitionend` + // go back to version 0.5.2 of impress.js: + // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js + window.clearTimeout( stepEnterTimeout ); + stepEnterTimeout = window.setTimeout( function() { + onStepEnter( activeStep ); + }, duration + delay ); + + return el; + }; + + // `prev` API function goes to previous step (in document order) + // `event` is optional, may contain the event that caused the need to call prev() + var prev = function( origEvent ) { + var prev = steps.indexOf( activeStep ) - 1; + prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ]; + + return goto( prev, undefined, "prev", origEvent ); + }; + + // `next` API function goes to next step (in document order) + // `event` is optional, may contain the event that caused the need to call next() + var next = function( origEvent ) { + var next = steps.indexOf( activeStep ) + 1; + next = next < steps.length ? steps[ next ] : steps[ 0 ]; + + return goto( next, undefined, "next", origEvent ); + }; + + // Swipe for touch devices by @and3rson. + // Below we extend the api to control the animation between the currently + // active step and a presumed next/prev step. See touch plugin for + // an example of using this api. + + // Helper function + var interpolate = function( a, b, k ) { + return a + ( b - a ) * k; + }; + + // Animate a swipe. + // + // Pct is a value between -1.0 and +1.0, designating the current length + // of the swipe. + // + // If pct is negative, swipe towards the next() step, if positive, + // towards the prev() step. + // + // Note that pre-stepleave plugins such as goto can mess with what is a + // next() and prev() step, so we need to trigger the pre-stepleave event + // here, even if a swipe doesn't guarantee that the transition will + // actually happen. + // + // Calling swipe(), with any value of pct, won't in itself cause a + // transition to happen, this is just to animate the swipe. Once the + // transition is committed - such as at a touchend event - caller is + // responsible for also calling prev()/next() as appropriate. + // + // Note: For now, this function is made available to be used by the swipe plugin (which + // is the UI counterpart to this). It is a semi-internal API and intentionally not + // documented in DOCUMENTATION.md. + var swipe = function( pct ) { + if ( Math.abs( pct ) > 1 ) { + return; + } + + // Prepare & execute the preStepLeave event + var event = { target: activeStep, detail: {} }; + event.detail.swipe = pct; + + // Will be ignored within swipe animation, but just in case a plugin wants to read this, + // humor them + event.detail.transitionDuration = config.transitionDuration; + var idx; // Needed by jshint + if ( pct < 0 ) { + idx = steps.indexOf( activeStep ) + 1; + event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ]; + event.detail.reason = "next"; + } else if ( pct > 0 ) { + idx = steps.indexOf( activeStep ) - 1; + event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ]; + event.detail.reason = "prev"; + } else { + + // No move + return; + } + if ( execPreStepLeavePlugins( event ) === false ) { + + // If a preStepLeave plugin wants to abort the transition, don't animate a swipe + // For stop, this is probably ok. For substep, the plugin it self might want to do + // some animation, but that's not the current implementation. + return false; + } + var nextElement = event.detail.next; + + var nextStep = stepsData[ "impress-" + nextElement.id ]; + + // If the same step is re-selected, force computing window scaling, + var nextScale = nextStep.scale * windowScale; + var k = Math.abs( pct ); + + var interpolatedStep = { + translate: { + x: interpolate( currentState.translate.x, -nextStep.translate.x, k ), + y: interpolate( currentState.translate.y, -nextStep.translate.y, k ), + z: interpolate( currentState.translate.z, -nextStep.translate.z, k ) + }, + rotate: { + x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ), + y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ), + z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ), + + // Unfortunately there's a discontinuity if rotation order changes. Nothing I + // can do about it? + order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order + }, + scale: interpolate( currentState.scale * windowScale, nextScale, k ) + }; + + css( root, { + + // To keep the perspective look similar for different scales + // we need to 'scale' the perspective, too + perspective: config.perspective / interpolatedStep.scale + "px", + transform: scale( interpolatedStep.scale ), + transitionDuration: "0ms", + transitionDelay: "0ms" + } ); + + css( canvas, { + transform: rotate( interpolatedStep.rotate, true ) + + translate( interpolatedStep.translate ), + transitionDuration: "0ms", + transitionDelay: "0ms" + } ); + }; + + // Teardown impress + // Resets the DOM to the state it was before impress().init() was called. + // (If you called impress(rootId).init() for multiple different rootId's, then you must + // also call tear() once for each of them.) + var tear = function() { + lib.gc.teardown(); + delete roots[ "impress-root-" + rootId ]; + }; + + // Adding some useful classes to step elements. + // + // All the steps that have not been shown yet are given `future` class. + // When the step is entered the `future` class is removed and the `present` + // class is given. When the step is left `present` class is replaced with + // `past` class. + // + // So every step element is always in one of three possible states: + // `future`, `present` and `past`. + // + // There classes can be used in CSS to style different types of steps. + // For example the `present` class can be used to trigger some custom + // animations when step is shown. + lib.gc.addEventListener( root, "impress:init", function() { + + // STEP CLASSES + steps.forEach( function( step ) { + step.classList.add( "future" ); + } ); + + lib.gc.addEventListener( root, "impress:stepenter", function( event ) { + event.target.classList.remove( "past" ); + event.target.classList.remove( "future" ); + event.target.classList.add( "present" ); + }, false ); + + lib.gc.addEventListener( root, "impress:stepleave", function( event ) { + event.target.classList.remove( "present" ); + event.target.classList.add( "past" ); + }, false ); + + }, false ); + + // Adding hash change support. + lib.gc.addEventListener( root, "impress:init", function() { + + // Last hash detected + var lastHash = ""; + + // `#/step-id` is used instead of `#step-id` to prevent default browser + // scrolling to element in hash. + // + // And it has to be set after animation finishes, because in Chrome it + // makes transtion laggy. + // BUG: http://code.google.com/p/chromium/issues/detail?id=62820 + lib.gc.addEventListener( root, "impress:stepenter", function( event ) { + window.location.hash = lastHash = "#/" + event.target.id; + }, false ); + + lib.gc.addEventListener( window, "hashchange", function() { + + // When the step is entered hash in the location is updated + // (just few lines above from here), so the hash change is + // triggered and we would call `goto` again on the same element. + // + // To avoid this we store last entered hash and compare. + if ( window.location.hash !== lastHash ) { + goto( lib.util.getElementFromHash() ); + } + }, false ); + + // START + // by selecting step defined in url or first step of the presentation + goto( lib.util.getElementFromHash() || steps[ 0 ], 0 ); + }, false ); + + body.classList.add( "impress-disabled" ); + + // Store and return API for given impress.js root element + return ( roots[ "impress-root-" + rootId ] = { + init: init, + goto: goto, + next: next, + prev: prev, + swipe: swipe, + tear: tear, + lib: lib + } ); + + }; + + // Flag that can be used in JS to check if browser have passed the support test + impress.supported = impressSupported; + + // ADD and INIT LIBRARIES + // Library factories are defined in src/lib/*.js, and register themselves by calling + // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment + // the API with library functions when client calls impress(rootId). + // See src/lib/README.md for clearer example. + // (Advanced usage: For different values of rootId, a different instance of the libaries are + // generated, in case they need to hold different state for different root elements.) + var libraryFactories = {}; + impress.addLibraryFactory = function( obj ) { + for ( var libname in obj ) { + if ( obj.hasOwnProperty( libname ) ) { + libraryFactories[ libname ] = obj[ libname ]; + } + } + }; + + // Call each library factory, and return the lib object that is added to the api. + var initLibraries = function( rootId ) { //jshint ignore:line + var lib = {}; + for ( var libname in libraryFactories ) { + if ( libraryFactories.hasOwnProperty( libname ) ) { + if ( lib[ libname ] !== undefined ) { + throw "impress.js ERROR: Two libraries both tried to use libname: " + libname; + } + lib[ libname ] = libraryFactories[ libname ]( rootId ); + } + } + return lib; + }; + + // `addPreInitPlugin` allows plugins to register a function that should + // be run (synchronously) at the beginning of init, before + // impress().init() itself executes. + impress.addPreInitPlugin = function( plugin, weight ) { + weight = parseInt( weight ) || 10; + if ( weight <= 0 ) { + throw "addPreInitPlugin: weight must be a positive integer"; + } + + if ( preInitPlugins[ weight ] === undefined ) { + preInitPlugins[ weight ] = []; + } + preInitPlugins[ weight ].push( plugin ); + }; + + // Called at beginning of init, to execute all pre-init plugins. + var execPreInitPlugins = function( root ) { //jshint ignore:line + for ( var i = 0; i < preInitPlugins.length; i++ ) { + var thisLevel = preInitPlugins[ i ]; + if ( thisLevel !== undefined ) { + for ( var j = 0; j < thisLevel.length; j++ ) { + thisLevel[ j ]( root ); + } + } + } + }; + + // `addPreStepLeavePlugin` allows plugins to register a function that should + // be run (synchronously) at the beginning of goto() + impress.addPreStepLeavePlugin = function( plugin, weight ) { //jshint ignore:line + weight = parseInt( weight ) || 10; + if ( weight <= 0 ) { + throw "addPreStepLeavePlugin: weight must be a positive integer"; + } + + if ( preStepLeavePlugins[ weight ] === undefined ) { + preStepLeavePlugins[ weight ] = []; + } + preStepLeavePlugins[ weight ].push( plugin ); + }; + + // Called at beginning of goto(), to execute all preStepLeave plugins. + var execPreStepLeavePlugins = function( event ) { //jshint ignore:line + for ( var i = 0; i < preStepLeavePlugins.length; i++ ) { + var thisLevel = preStepLeavePlugins[ i ]; + if ( thisLevel !== undefined ) { + for ( var j = 0; j < thisLevel.length; j++ ) { + if ( thisLevel[ j ]( event ) === false ) { + + // If a plugin returns false, the stepleave event (and related transition) + // is aborted + return false; + } + } + } + } + }; + +} )( document, window ); + +// THAT'S ALL FOLKS! +// +// Thanks for reading it all. +// Or thanks for scrolling down and reading the last part. +// +// I've learnt a lot when building impress.js and I hope this code and comments +// will help somebody learn at least some part of it. diff --git a/src/main/java/com/fr/plugin/easyslides/web/impress-plugins.js b/src/main/java/com/fr/plugin/easyslides/web/impress-plugins.js new file mode 100644 index 0000000..70bffe3 --- /dev/null +++ b/src/main/java/com/fr/plugin/easyslides/web/impress-plugins.js @@ -0,0 +1,3384 @@ +// This file was automatically generated from files in src/ directory. + +/*! Licensed under MIT License - http://github.com/impress/impress.js */ + +/** + * Garbage collection utility + * + * This library allows plugins to add elements and event listeners they add to the DOM. The user + * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that + * the document is in the state it was before calling `impress().init()`. + * + * In addition to just adding elements and event listeners to the garbage collector, plugins + * can also register callback functions to do arbitrary cleanup upon teardown. + * + * Henrik Ingo (c) 2016 + * MIT License + */ + +( function( document, window ) { + "use strict"; + var roots = []; + var rootsCount = 0; + var startingState = { roots: [] }; + + var libraryFactory = function( rootId ) { + if ( roots[ rootId ] ) { + return roots[ rootId ]; + } + + // Per root global variables (instance variables?) + var elementList = []; + var eventListenerList = []; + var callbackList = []; + + recordStartingState( rootId ); + + // LIBRARY FUNCTIONS + // Definitions of the library functions we return as an object at the end + + // `pushElement` adds a DOM element to the gc stack + var pushElement = function( element ) { + elementList.push( element ); + }; + + // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement + var appendChild = function( parent, element ) { + parent.appendChild( element ); + pushElement( element ); + }; + + // `pushEventListener` adds an event listener to the gc stack + var pushEventListener = function( target, type, listenerFunction ) { + eventListenerList.push( { target:target, type:type, listener:listenerFunction } ); + }; + + // `addEventListener` combines DOM addEventListener with gc.pushEventListener + var addEventListener = function( target, type, listenerFunction ) { + target.addEventListener( type, listenerFunction ); + pushEventListener( target, type, listenerFunction ); + }; + + // `pushCallback` If the above utilities are not enough, plugins can add their own callback + // function to do arbitrary things. + var pushCallback = function( callback ) { + callbackList.push( callback ); + }; + pushCallback( function( rootId ) { resetStartingState( rootId ); } ); + + // `teardown` will + // - execute all callbacks in LIFO order + // - call `removeChild` on all DOM elements in LIFO order + // - call `removeEventListener` on all event listeners in LIFO order + // The goal of a teardown is to return to the same state that the DOM was before + // `impress().init()` was called. + var teardown = function() { + + // Execute the callbacks in LIFO order + var i; // Needed by jshint + for ( i = callbackList.length - 1; i >= 0; i-- ) { + callbackList[ i ]( rootId ); + } + callbackList = []; + for ( i = 0; i < elementList.length; i++ ) { + elementList[ i ].parentElement.removeChild( elementList[ i ] ); + } + elementList = []; + for ( i = 0; i < eventListenerList.length; i++ ) { + var target = eventListenerList[ i ].target; + var type = eventListenerList[ i ].type; + var listener = eventListenerList[ i ].listener; + target.removeEventListener( type, listener ); + } + }; + + var lib = { + pushElement: pushElement, + appendChild: appendChild, + pushEventListener: pushEventListener, + addEventListener: addEventListener, + pushCallback: pushCallback, + teardown: teardown + }; + roots[ rootId ] = lib; + rootsCount++; + return lib; + }; + + // Let impress core know about the existence of this library + window.impress.addLibraryFactory( { gc: libraryFactory } ); + + // CORE INIT + // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init() + // For the purposes of teardown(), we can use this as an opportunity to save the state + // of a few things in the DOM in their virgin state, before impress().init() did anything. + // Note: These could also be recorded by the code in impress.js core as these values + // are changed, but in an effort to not deviate too much from upstream, I'm adding + // them here rather than the core itself. + var recordStartingState = function( rootId ) { + startingState.roots[ rootId ] = {}; + startingState.roots[ rootId ].steps = []; + + // Record whether the steps have an id or not + var steps = document.getElementById( rootId ).querySelectorAll( ".step" ); + for ( var i = 0; i < steps.length; i++ ) { + var el = steps[ i ]; + startingState.roots[ rootId ].steps.push( { + el: el, + id: el.getAttribute( "id" ) + } ); + } + + // In the rare case of multiple roots, the following is changed on first init() and + // reset at last tear(). + if ( rootsCount === 0 ) { + startingState.body = {}; + + // It is customary for authors to set body.class="impress-not-supported" as a starting + // value, which can then be removed by impress().init(). But it is not required. + // Remember whether it was there or not. + if ( document.body.classList.contains( "impress-not-supported" ) ) { + startingState.body.impressNotSupported = true; + } else { + startingState.body.impressNotSupported = false; + } + + // If there's a element, its contents will be overwritten by init + var metas = document.head.querySelectorAll( "meta" ); + for ( i = 0; i < metas.length; i++ ) { + var m = metas[ i ]; + if ( m.name === "viewport" ) { + startingState.meta = m.content; + } + } + } + }; + + // CORE TEARDOWN + var resetStartingState = function( rootId ) { + + // Reset body element + document.body.classList.remove( "impress-enabled" ); + document.body.classList.remove( "impress-disabled" ); + + var root = document.getElementById( rootId ); + var activeId = root.querySelector( ".active" ).id; + document.body.classList.remove( "impress-on-" + activeId ); + + document.documentElement.style.height = ""; + document.body.style.height = ""; + document.body.style.overflow = ""; + + // Remove style values from the root and step elements + // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original + // values. A more sophisticated implementation could keep track of original values and then + // reset those. + var steps = root.querySelectorAll( ".step" ); + for ( var i = 0; i < steps.length; i++ ) { + steps[ i ].classList.remove( "future" ); + steps[ i ].classList.remove( "past" ); + steps[ i ].classList.remove( "present" ); + steps[ i ].classList.remove( "active" ); + steps[ i ].style.position = ""; + steps[ i ].style.transform = ""; + steps[ i ].style[ "transform-style" ] = ""; + } + root.style.position = ""; + root.style[ "transform-origin" ] = ""; + root.style.transition = ""; + root.style[ "transform-style" ] = ""; + root.style.top = ""; + root.style.left = ""; + root.style.transform = ""; + + // Reset id of steps ("step-1" id's are auto generated) + steps = startingState.roots[ rootId ].steps; + var step; + while ( step = steps.pop() ) { + if ( step.id === null ) { + step.el.removeAttribute( "id" ); + } else { + step.el.setAttribute( "id", step.id ); + } + } + delete startingState.roots[ rootId ]; + + // Move step div elements away from canvas, then delete canvas + // Note: There's an implicit assumption here that the canvas div is the only child element + // of the root div. If there would be something else, it's gonna be lost. + var canvas = root.firstChild; + var canvasHTML = canvas.innerHTML; + root.innerHTML = canvasHTML; + + if ( roots[ rootId ] !== undefined ) { + delete roots[ rootId ]; + rootsCount--; + } + if ( rootsCount === 0 ) { + + // In the rare case that more than one impress root elements were initialized, these + // are only reset when all are uninitialized. + document.body.classList.remove( "impress-supported" ); + if ( startingState.body.impressNotSupported ) { + document.body.classList.add( "impress-not-supported" ); + } + + // We need to remove or reset the meta element inserted by impress.js + var metas = document.head.querySelectorAll( "meta" ); + for ( i = 0; i < metas.length; i++ ) { + var m = metas[ i ]; + if ( m.name === "viewport" ) { + if ( startingState.meta !== undefined ) { + m.content = startingState.meta; + } else { + m.parentElement.removeChild( m ); + } + } + } + } + + }; + +} )( document, window ); + +/** + * Common utility functions + * + * Copyright 2011-2012 Bartek Szopka (@bartaz) + * Henrik Ingo (c) 2016 + * MIT License + */ + +( function( document, window ) { + "use strict"; + var roots = []; + + var libraryFactory = function( rootId ) { + if ( roots[ rootId ] ) { + return roots[ rootId ]; + } + + // `$` returns first element for given CSS `selector` in the `context` of + // the given element or whole document. + var $ = function( selector, context ) { + context = context || document; + return context.querySelector( selector ); + }; + + // `$$` return an array of elements for given CSS `selector` in the `context` of + // the given element or whole document. + var $$ = function( selector, context ) { + context = context || document; + return arrayify( context.querySelectorAll( selector ) ); + }; + + // `arrayify` takes an array-like object and turns it into real Array + // to make all the Array.prototype goodness available. + var arrayify = function( a ) { + return [].slice.call( a ); + }; + + // `byId` returns element with given `id` - you probably have guessed that ;) + var byId = function( id ) { + return document.getElementById( id ); + }; + + // `getElementFromHash` returns an element located by id from hash part of + // window location. + var getElementFromHash = function() { + + // Get id from url # by removing `#` or `#/` from the beginning, + // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work + return byId( window.location.hash.replace( /^#\/?/, "" ) ); + }; + + // Throttling function calls, by Remy Sharp + // http://remysharp.com/2010/07/21/throttling-function-calls/ + var throttle = function( fn, delay ) { + var timer = null; + return function() { + var context = this, args = arguments; + window.clearTimeout( timer ); + timer = window.setTimeout( function() { + fn.apply( context, args ); + }, delay ); + }; + }; + + // `toNumber` takes a value given as `numeric` parameter and tries to turn + // it into a number. If it is not possible it returns 0 (or other value + // given as `fallback`). + var toNumber = function( numeric, fallback ) { + return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric ); + }; + + // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data + // and triggers it on element given as `el`. + var triggerEvent = function( el, eventName, detail ) { + var event = document.createEvent( "CustomEvent" ); + event.initCustomEvent( eventName, true, true, detail ); + el.dispatchEvent( event ); + }; + + var lib = { + $: $, + $$: $$, + arrayify: arrayify, + byId: byId, + getElementFromHash: getElementFromHash, + throttle: throttle, + toNumber: toNumber, + triggerEvent: triggerEvent + }; + roots[ rootId ] = lib; + return lib; + }; + + // Let impress core know about the existence of this library + window.impress.addLibraryFactory( { util: libraryFactory } ); + +} )( document, window ); + +/** + * Autoplay plugin - Automatically advance slideshow after N seconds + * + * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi + * Released under the MIT license. + */ +/* global clearTimeout, setTimeout, document */ + +( function( document ) { + "use strict"; + + var autoplayDefault = 0; + var currentStepTimeout = 0; + var api = null; + var timeoutHandle = null; + var root = null; + var util; + + // On impress:init, check whether there is a default setting, as well as + // handle step-1. + document.addEventListener( "impress:init", function( event ) { + util = event.detail.api.lib.util; + + // Getting API from event data instead of global impress().init(). + // You don't even need to know what is the id of the root element + // or anything. `impress:init` event data gives you everything you + // need to control the presentation that was just initialized. + api = event.detail.api; + root = event.target; + + // Element attributes starting with "data-", become available under + // element.dataset. In addition hyphenized words become camelCased. + var data = root.dataset; + + if ( data.autoplay ) { + autoplayDefault = util.toNumber( data.autoplay, 0 ); + } + + var toolbar = document.querySelector( "#impress-toolbar" ); + if ( toolbar ) { + addToolbarButton( toolbar ); + } + + api.lib.gc.pushCallback( function() { + clearTimeout( timeoutHandle ); + } ); + + // Note that right after impress:init event, also impress:stepenter is + // triggered for the first slide, so that's where code flow continues. + }, false ); + + document.addEventListener( "impress:autoplay:pause", function( event ) { + status = "paused"; + reloadTimeout( event ); + }, false ); + + document.addEventListener( "impress:autoplay:play", function( event ) { + status = "playing"; + reloadTimeout( event ); + }, false ); + + // If default autoplay time was defined in the presentation root, or + // in this step, set timeout. + var reloadTimeout = function( event ) { + var step = event.target; + currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault ); + if ( status === "paused" ) { + setAutoplayTimeout( 0 ); + } else { + setAutoplayTimeout( currentStepTimeout ); + } + }; + + document.addEventListener( "impress:stepenter", function( event ) { + reloadTimeout( event ); + }, false ); + + document.addEventListener( "impress:substep:enter", function( event ) { + reloadTimeout( event ); + }, false ); + + /** + * Set timeout after which we move to next() step. + */ + var setAutoplayTimeout = function( timeout ) { + if ( timeoutHandle ) { + clearTimeout( timeoutHandle ); + } + + if ( timeout > 0 ) { + timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 ); + } + setButtonText(); + }; + + /*** Toolbar plugin integration *******************************************/ + var status = "not clicked"; + var toolbarButton = null; + + var makeDomElement = function( html ) { + var tempDiv = document.createElement( "div" ); + tempDiv.innerHTML = html; + return tempDiv.firstChild; + }; + + var toggleStatus = function() { + if ( currentStepTimeout > 0 && status !== "paused" ) { + status = "paused"; + } else { + status = "playing"; + } + }; + + var getButtonText = function() { + if ( currentStepTimeout > 0 && status !== "paused" ) { + return "||"; // Pause + } else { + return "▶"; // Play + } + }; + + var setButtonText = function() { + if ( toolbarButton ) { + + // Keep button size the same even if label content is changing + var buttonWidth = toolbarButton.offsetWidth; + var buttonHeight = toolbarButton.offsetHeight; + toolbarButton.innerHTML = getButtonText(); + if ( !toolbarButton.style.width ) { + toolbarButton.style.width = buttonWidth + "px"; + } + if ( !toolbarButton.style.height ) { + toolbarButton.style.height = buttonHeight + "px"; + } + } + }; + + var addToolbarButton = function( toolbar ) { + var html = '"; // jshint ignore:line + toolbarButton = makeDomElement( html ); + toolbarButton.addEventListener( "click", function() { + toggleStatus(); + if ( status === "playing" ) { + if ( autoplayDefault === 0 ) { + autoplayDefault = 7; + } + if ( currentStepTimeout === 0 ) { + currentStepTimeout = autoplayDefault; + } + setAutoplayTimeout( currentStepTimeout ); + } else if ( status === "paused" ) { + setAutoplayTimeout( 0 ); + } + } ); + + util.triggerEvent( toolbar, "impress:toolbar:appendChild", + { group: 10, element: toolbarButton } ); + }; + +} )( document ); + +/** + * Blackout plugin + * + * Press b or . to hide all slides, and b or . again to show them. + * Also navigating to a different slide will show them again (impress:stepleave). + * + * Copyright 2014 @Strikeskids + * Released under the MIT license. + */ +/* global document */ + +( function( document ) { + "use strict"; + + var canvas = null; + var blackedOut = false; + var util = null; + var root = null; + var api = null; + + // While waiting for a shared library of utilities, copying these 2 from main impress.js + var css = function( el, props ) { + var key, pkey; + for ( key in props ) { + if ( props.hasOwnProperty( key ) ) { + pkey = pfx( key ); + if ( pkey !== null ) { + el.style[ pkey ] = props[ key ]; + } + } + } + return el; + }; + + var pfx = ( function() { + + var style = document.createElement( "dummy" ).style, + prefixes = "Webkit Moz O ms Khtml".split( " " ), + memory = {}; + + return function( prop ) { + if ( typeof memory[ prop ] === "undefined" ) { + + var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), + props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " ); + + memory[ prop ] = null; + for ( var i in props ) { + if ( style[ props[ i ] ] !== undefined ) { + memory[ prop ] = props[ i ]; + break; + } + } + + } + + return memory[ prop ]; + }; + + } )(); + + var removeBlackout = function() { + if ( blackedOut ) { + css( canvas, { + display: "block" + } ); + blackedOut = false; + util.triggerEvent( root, "impress:autoplay:play", {} ); + } + }; + + var blackout = function() { + if ( blackedOut ) { + removeBlackout(); + } else { + css( canvas, { + display: ( blackedOut = !blackedOut ) ? "none" : "block" + } ); + blackedOut = true; + util.triggerEvent( root, "impress:autoplay:pause", {} ); + } + }; + + // Wait for impress.js to be initialized + document.addEventListener( "impress:init", function( event ) { + api = event.detail.api; + util = api.lib.util; + root = event.target; + canvas = root.firstElementChild; + var gc = api.lib.gc; + var util = api.lib.util; + + gc.addEventListener( document, "keydown", function( event ) { + + // Accept b or . -> . is sent by presentation remote controllers + if ( event.keyCode === 66 || event.keyCode === 190 ) { + event.preventDefault(); + if ( !blackedOut ) { + blackout(); + } else { + removeBlackout(); + } + } + }, false ); + + gc.addEventListener( document, "keyup", function( event ) { + + // Accept b or . -> . is sent by presentation remote controllers + if ( event.keyCode === 66 || event.keyCode === 190 ) { + event.preventDefault(); + } + }, false ); + + }, false ); + + document.addEventListener( "impress:stepleave", function() { + removeBlackout(); + }, false ); + +} )( document ); + + +/** + * Extras Plugin + * + * This plugin performs initialization (like calling mermaid.initialize()) + * for the extras/ plugins if they are loaded into a presentation. + * + * See README.md for details. + * + * Copyright 2016 Henrik Ingo (@henrikingo) + * Released under the MIT license. + */ +/* global markdown, hljs, mermaid, impress, document, window */ + +( function( document, window ) { + "use strict"; + + var preInit = function() { + if ( window.markdown ) { + + // Unlike the other extras, Markdown.js doesn't by default do anything in + // particular. We do it ourselves here. + // In addition, we use "-----" as a delimiter for new slide. + + // Query all .markdown elements and translate to HTML + var markdownDivs = document.querySelectorAll( ".markdown" ); + for ( var idx = 0; idx < markdownDivs.length; idx++ ) { + var element = markdownDivs[ idx ]; + + var slides = element.textContent.split( /^-----$/m ); + var i = slides.length - 1; + element.innerHTML = markdown.toHTML( slides[ i ] ); + + // If there's an id, unset it for last, and all other, elements, + // and then set it for the first. + var id = null; + if ( element.id ) { + id = element.id; + element.id = ""; + } + i--; + while ( i >= 0 ) { + var newElement = element.cloneNode( false ); + newElement.innerHTML = markdown.toHTML( slides[ i ] ); + element.parentNode.insertBefore( newElement, element ); + element = newElement; + i--; + } + if ( id !== null ) { + element.id = id; + } + } + } // Markdown + + if ( window.hljs ) { + hljs.initHighlightingOnLoad(); + } + + if ( window.mermaid ) { + mermaid.initialize( { startOnLoad:true } ); + } + }; + + // Register the plugin to be called in pre-init phase + // Note: Markdown.js should run early/first, because it creates new div elements. + // So add this with a lower-than-default weight. + impress.addPreInitPlugin( preInit, 1 ); + +} )( document, window ); + + +/** + * Form support + * + * Functionality to better support use of input, textarea, button... elements in a presentation. + * + * This plugin does two things: + * + * Set stopPropagation on any element that might take text input. This allows users to type, for + * example, the letter 'P' into a form field, without causing the presenter console to spring up. + * + * On impress:stepleave, de-focus any potentially active + * element. This is to prevent the focus from being left in a form element that is no longer visible + * in the window, and user therefore typing garbage into the form. + * + * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and + * in particular the navigation plugin, unfortunately must fully take control of the tab key, + * otherwise a user could cause the browser to scroll to a link or button that's not on the current + * step. However, it could be possible to allow tab navigation between form elements, as long as + * they are on the active step. This is a topic for further study. + * + * Copyright 2016 Henrik Ingo + * MIT License + */ +/* global document */ +( function( document ) { + "use strict"; + var root; + var api; + + document.addEventListener( "impress:init", function( event ) { + root = event.target; + api = event.detail.api; + var gc = api.lib.gc; + + var selectors = [ "input", "textarea", "select", "[contenteditable=true]" ]; + for ( var selector of selectors ) { + var elements = document.querySelectorAll( selector ); + if ( !elements ) { + continue; + } + + for ( var i = 0; i < elements.length; i++ ) { + var e = elements[ i ]; + gc.addEventListener( e, "keydown", function( event ) { + event.stopPropagation(); + } ); + gc.addEventListener( e, "keyup", function( event ) { + event.stopPropagation(); + } ); + } + } + }, false ); + + document.addEventListener( "impress:stepleave", function() { + document.activeElement.blur(); + }, false ); + +} )( document ); + + +/** + * Fullscreen plugin + * + * Press F5 to enter fullscreen and ESC to exit fullscreen mode. + * + * Copyright 2019 @giflw + * Released under the MIT license. + */ +/* global document */ + +( function( document ) { + "use strict"; + + function enterFullscreen() { + var elem = document.documentElement; + if ( !document.fullscreenElement ) { + elem.requestFullscreen(); + } + } + + function exitFullscreen() { + if ( document.fullscreenElement ) { + document.exitFullscreen(); + } + } + + // Wait for impress.js to be initialized + document.addEventListener( "impress:init", function( event ) { + var api = event.detail.api; + var root = event.target; + var gc = api.lib.gc; + var util = api.lib.util; + + gc.addEventListener( document, "keydown", function( event ) { + + // 116 (F5) is sent by presentation remote controllers + if ( event.code === "F5" ) { + event.preventDefault(); + enterFullscreen(); + util.triggerEvent( root.querySelector( ".active" ), "impress:steprefresh" ); + } + + // 27 (Escape) is sent by presentation remote controllers + if ( event.key === "Escape" || event.key === "F5" ) { + event.preventDefault(); + exitFullscreen(); + util.triggerEvent( root.querySelector( ".active" ), "impress:steprefresh" ); + } + }, false ); + + util.triggerEvent( document, "impress:help:add", + { command: "F5 / ESC", text: "Fullscreen: Enter / Exit", row: 200 } ); + + }, false ); + +} )( document ); + + +/** + * Goto Plugin + * + * The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave, + * and will alter the destination where to transition next. + * + * Example: + * + * + *
+ * + * + *
+ * + * + *
+ * + * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table + * of what strings to use for each key. + * + * Copyright 2016-2017 Henrik Ingo (@henrikingo) + * Released under the MIT license. + */ +/* global window, document, impress */ + +( function( document, window ) { + "use strict"; + var lib; + + document.addEventListener( "impress:init", function( event ) { + lib = event.detail.api.lib; + }, false ); + + var isNumber = function( numeric ) { + return !isNaN( numeric ); + }; + + var goto = function( event ) { + if ( ( !event ) || ( !event.target ) ) { + return; + } + + var data = event.target.dataset; + var steps = document.querySelectorAll( ".step" ); + + // Data-goto-key-list="" & data-goto-next-list="" ////////////////////////////////////////// + if ( data.gotoKeyList !== undefined && + data.gotoNextList !== undefined && + event.origEvent !== undefined && + event.origEvent.key !== undefined ) { + var keylist = data.gotoKeyList.split( " " ); + var nextlist = data.gotoNextList.split( " " ); + + if ( keylist.length !== nextlist.length ) { + window.console.log( + "impress goto plugin: data-goto-key-list and data-goto-next-list don't match:" + ); + window.console.log( keylist ); + window.console.log( nextlist ); + + // Don't return, allow the other categories to work despite this error + } else { + var index = keylist.indexOf( event.origEvent.key ); + if ( index >= 0 ) { + var next = nextlist[ index ]; + if ( isNumber( next ) ) { + event.detail.next = steps[ next ]; + + // If the new next element has its own transitionDuration, we're responsible + // for setting that on the event as well + event.detail.transitionDuration = lib.util.toNumber( + event.detail.next.dataset.transitionDuration, + event.detail.transitionDuration + ); + return; + } else { + var newTarget = document.getElementById( next ); + if ( newTarget && newTarget.classList.contains( "step" ) ) { + event.detail.next = newTarget; + event.detail.transitionDuration = lib.util.toNumber( + event.detail.next.dataset.transitionDuration, + event.detail.transitionDuration + ); + return; + } else { + window.console.log( "impress goto plugin: " + next + + " is not a step in this impress presentation." ); + } + } + } + } + } + + // Data-goto-next="" & data-goto-prev="" /////////////////////////////////////////////////// + + // Handle event.target data-goto-next attribute + if ( isNumber( data.gotoNext ) && event.detail.reason === "next" ) { + event.detail.next = steps[ data.gotoNext ]; + + // If the new next element has its own transitionDuration, we're responsible for setting + // that on the event as well + event.detail.transitionDuration = lib.util.toNumber( + event.detail.next.dataset.transitionDuration, event.detail.transitionDuration + ); + return; + } + if ( data.gotoNext && event.detail.reason === "next" ) { + var newTarget = document.getElementById( data.gotoNext ); // jshint ignore:line + if ( newTarget && newTarget.classList.contains( "step" ) ) { + event.detail.next = newTarget; + event.detail.transitionDuration = lib.util.toNumber( + event.detail.next.dataset.transitionDuration, + event.detail.transitionDuration + ); + return; + } else { + window.console.log( "impress goto plugin: " + data.gotoNext + + " is not a step in this impress presentation." ); + } + } + + // Handle event.target data-goto-prev attribute + if ( isNumber( data.gotoPrev ) && event.detail.reason === "prev" ) { + event.detail.next = steps[ data.gotoPrev ]; + event.detail.transitionDuration = lib.util.toNumber( + event.detail.next.dataset.transitionDuration, event.detail.transitionDuration + ); + return; + } + if ( data.gotoPrev && event.detail.reason === "prev" ) { + var newTarget = document.getElementById( data.gotoPrev ); // jshint ignore:line + if ( newTarget && newTarget.classList.contains( "step" ) ) { + event.detail.next = newTarget; + event.detail.transitionDuration = lib.util.toNumber( + event.detail.next.dataset.transitionDuration, event.detail.transitionDuration + ); + return; + } else { + window.console.log( "impress goto plugin: " + data.gotoPrev + + " is not a step in this impress presentation." ); + } + } + + // Data-goto="" /////////////////////////////////////////////////////////////////////////// + + // Handle event.target data-goto attribute + if ( isNumber( data.goto ) ) { + event.detail.next = steps[ data.goto ]; + event.detail.transitionDuration = lib.util.toNumber( + event.detail.next.dataset.transitionDuration, event.detail.transitionDuration + ); + return; + } + if ( data.goto ) { + var newTarget = document.getElementById( data.goto ); // jshint ignore:line + if ( newTarget && newTarget.classList.contains( "step" ) ) { + event.detail.next = newTarget; + event.detail.transitionDuration = lib.util.toNumber( + event.detail.next.dataset.transitionDuration, event.detail.transitionDuration + ); + return; + } else { + window.console.log( "impress goto plugin: " + data.goto + + " is not a step in this impress presentation." ); + } + } + }; + + // Register the plugin to be called in pre-stepleave phase + impress.addPreStepLeavePlugin( goto ); + +} )( document, window ); + + +/** + * Help popup plugin + * + * Example: + * + * + *
+ * + * For developers: + * + * Typical use for this plugin, is for plugins that support some keypress, to add a line + * to the help popup produced by this plugin. For example "P: Presenter console". + * + * Copyright 2016 Henrik Ingo (@henrikingo) + * Released under the MIT license. + */ +/* global window, document */ + +( function( document, window ) { + "use strict"; + var rows = []; + var timeoutHandle; + + var triggerEvent = function( el, eventName, detail ) { + var event = document.createEvent( "CustomEvent" ); + event.initCustomEvent( eventName, true, true, detail ); + el.dispatchEvent( event ); + }; + + var renderHelpDiv = function() { + var helpDiv = document.getElementById( "impress-help" ); + if ( helpDiv ) { + var html = []; + for ( var row in rows ) { + for ( var arrayItem in row ) { + html.push( rows[ row ][ arrayItem ] ); + } + } + if ( html ) { + helpDiv.innerHTML = "\n" + html.join( "\n" ) + "
\n"; + } + } + }; + + var toggleHelp = function() { + var helpDiv = document.getElementById( "impress-help" ); + if ( !helpDiv ) { + return; + } + + if ( helpDiv.style.display === "block" ) { + helpDiv.style.display = "none"; + } else { + helpDiv.style.display = "block"; + window.clearTimeout( timeoutHandle ); + } + }; + + document.addEventListener( "keyup", function( event ) { + + if ( event.keyCode === 72 || event.keyCode === 191 ) { // "h" || "?" + event.preventDefault(); + toggleHelp(); + } + }, false ); + + // API + // Other plugins can add help texts, typically if they support an action on a keypress. + /** + * Add a help text to the help popup. + * + * :param: e.detail.command Example: "H" + * :param: e.detail.text Example: "Show this help." + * :param: e.detail.row Row index from 0 to 9 where to place this help text. Example: 0 + */ + document.addEventListener( "impress:help:add", function( e ) { + + // The idea is for the sender of the event to supply a unique row index, used for sorting. + // But just in case two plugins would ever use the same row index, we wrap each row into + // its own array. If there are more than one entry for the same index, they are shown in + // first come, first serve ordering. + var rowIndex = e.detail.row; + if ( typeof rows[ rowIndex ] !== "object" || !rows[ rowIndex ].isArray ) { + rows[ rowIndex ] = []; + } + rows[ e.detail.row ].push( "" + e.detail.command + "" + + e.detail.text + "" ); + renderHelpDiv(); + } ); + + document.addEventListener( "impress:init", function( e ) { + renderHelpDiv(); + + // At start, show the help for 7 seconds. + var helpDiv = document.getElementById( "impress-help" ); + if ( helpDiv ) { + helpDiv.style.display = "block"; + timeoutHandle = window.setTimeout( function() { + var helpDiv = document.getElementById( "impress-help" ); + helpDiv.style.display = "none"; + }, 7000 ); + + // Regster callback to empty the help div on teardown + var api = e.detail.api; + api.lib.gc.pushCallback( function() { + window.clearTimeout( timeoutHandle ); + helpDiv.style.display = ""; + helpDiv.innerHTML = ""; + rows = []; + } ); + } + + // Use our own API to register the help text for "h" + triggerEvent( document, "impress:help:add", + { command: "H", text: "Show this help", row: 0 } ); + } ); + +} )( document, window ); + + +/** + * Adds a presenter console to impress.js + * + * MIT Licensed, see license.txt. + * + * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt) + * + * version: 1.3-dev + * + */ + +// This file contains so much HTML, that we will just respectfully disagree about js +/* jshint quotmark:single */ +/* global navigator, top, setInterval, clearInterval, document, window */ + +( function( document, window ) { + 'use strict'; + + // TODO: Move this to src/lib/util.js + var triggerEvent = function( el, eventName, detail ) { + var event = document.createEvent( 'CustomEvent' ); + event.initCustomEvent( eventName, true, true, detail ); + el.dispatchEvent( event ); + }; + + // Create Language object depending on browsers language setting + var lang; + switch ( navigator.language ) { + case 'de': + lang = { + 'noNotes': '
Keine Notizen hierzu
', + 'restart': 'Neustart', + 'clickToOpen': 'Klicken um Sprecherkonsole zu öffnen', + 'prev': 'zurück', + 'next': 'weiter', + 'loading': 'initalisiere', + 'ready': 'Bereit', + 'moving': 'in Bewegung', + 'useAMPM': false + }; + break; + case 'en': // jshint ignore:line + default : // jshint ignore:line + lang = { + 'noNotes': '
No notes for this step
', + 'restart': 'Restart', + 'clickToOpen': 'Click to open speaker console', + 'prev': 'Prev', + 'next': 'Next', + 'loading': 'Loading', + 'ready': 'Ready', + 'moving': 'Moving', + 'useAMPM': false + }; + break; + } + + // Settings to set iframe in speaker console + const preViewDefaultFactor = 0.7; + const preViewMinimumFactor = 0.5; + const preViewGap = 4; + + // This is the default template for the speaker console window + const consoleTemplate = '' + + '' + + + // Order is important: If user provides a cssFile, those will win, because they're later + '{{cssStyle}}' + + '{{cssLink}}' + + '' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '' + + '' + + '
--:--
' + + '
00m 00s
' + + '
{{loading}}
' + + '
' + + ''; + + // Default css location + var cssFileOldDefault = 'css/impressConsole.css'; + var cssFile = undefined; // jshint ignore:line + + // Css for styling iframs on the console + var cssFileIframeOldDefault = 'css/iframe.css'; + var cssFileIframe = undefined; // jshint ignore:line + + // All console windows, so that you can call impressConsole() repeatedly. + var allConsoles = {}; + + // Zero padding helper function: + var zeroPad = function( i ) { + return ( i < 10 ? '0' : '' ) + i; + }; + + // The console object + var impressConsole = window.impressConsole = function( rootId ) { + + rootId = rootId || 'impress'; + + if ( allConsoles[ rootId ] ) { + return allConsoles[ rootId ]; + } + + // Root presentation elements + var root = document.getElementById( rootId ); + + var consoleWindow = null; + + var nextStep = function() { + var classes = ''; + var nextElement = document.querySelector( '.active' ); + + // Return to parents as long as there is no next sibling + while ( !nextElement.nextElementSibling && nextElement.parentNode ) { + nextElement = nextElement.parentNode; + } + nextElement = nextElement.nextElementSibling; + while ( nextElement ) { + classes = nextElement.attributes[ 'class' ]; + if ( classes && classes.value.indexOf( 'step' ) !== -1 ) { + consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.next; + return nextElement; + } + + if ( nextElement.firstElementChild ) { // First go into deep + nextElement = nextElement.firstElementChild; + } else { + + // Go to next sibling or through parents until there is a next sibling + while ( !nextElement.nextElementSibling && nextElement.parentNode ) { + nextElement = nextElement.parentNode; + } + nextElement = nextElement.nextElementSibling; + } + } + + // No next element. Pick the first + consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.restart; + return document.querySelector( '.step' ); + }; + + // Sync the notes to the step + var onStepLeave = function() { + if ( consoleWindow ) { + + // Set notes to next steps notes. + var newNotes = document.querySelector( '.active' ).querySelector( '.notes' ); + if ( newNotes ) { + newNotes = newNotes.innerHTML; + } else { + newNotes = lang.noNotes; + } + consoleWindow.document.getElementById( 'notes' ).innerHTML = newNotes; + + // Set the views + var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) ); + var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id; + var preSrc = baseURL + '#' + nextStep().id; + var slideView = consoleWindow.document.getElementById( 'slideView' ); + + // Setting them when they are already set causes glithes in Firefox, so check first: + if ( slideView.src !== slideSrc ) { + slideView.src = slideSrc; + } + var preView = consoleWindow.document.getElementById( 'preView' ); + if ( preView.src !== preSrc ) { + preView.src = preSrc; + } + + consoleWindow.document.getElementById( 'status' ).innerHTML = + '' + lang.moving + ''; + } + }; + + // Sync the previews to the step + var onStepEnter = function() { + if ( consoleWindow ) { + + // We do everything here again, because if you stopped the previos step to + // early, the onstepleave trigger is not called for that step, so + // we need this to sync things. + var newNotes = document.querySelector( '.active' ).querySelector( '.notes' ); + if ( newNotes ) { + newNotes = newNotes.innerHTML; + } else { + newNotes = lang.noNotes; + } + var notes = consoleWindow.document.getElementById( 'notes' ); + notes.innerHTML = newNotes; + notes.scrollTop = 0; + + // Set the views + var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) ); + var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id; + var preSrc = baseURL + '#' + nextStep().id; + var slideView = consoleWindow.document.getElementById( 'slideView' ); + + // Setting them when they are already set causes glithes in Firefox, so check first: + if ( slideView.src !== slideSrc ) { + slideView.src = slideSrc; + } + var preView = consoleWindow.document.getElementById( 'preView' ); + if ( preView.src !== preSrc ) { + preView.src = preSrc; + } + + consoleWindow.document.getElementById( 'status' ).innerHTML = + '' + lang.ready + ''; + } + }; + + // Sync substeps + var onSubstep = function( event ) { + if ( consoleWindow ) { + if ( event.detail.reason === 'next' ) { + onSubstepShow(); + } + if ( event.detail.reason === 'prev' ) { + onSubstepHide(); + } + } + }; + + var onSubstepShow = function() { + var slideView = consoleWindow.document.getElementById( 'slideView' ); + triggerEventInView( slideView, 'impress:substep:show' ); + }; + + var onSubstepHide = function() { + var slideView = consoleWindow.document.getElementById( 'slideView' ); + triggerEventInView( slideView, 'impress:substep:hide' ); + }; + + var triggerEventInView = function( frame, eventName, detail ) { + + // Note: Unfortunately Chrome does not allow createEvent on file:// URLs, so this won't + // work. This does work on Firefox, and should work if viewing the presentation on a + // http:// URL on Chrome. + var event = frame.contentDocument.createEvent( 'CustomEvent' ); + event.initCustomEvent( eventName, true, true, detail ); + frame.contentDocument.dispatchEvent( event ); + }; + + var spaceHandler = function() { + var notes = consoleWindow.document.getElementById( 'notes' ); + if ( notes.scrollTopMax - notes.scrollTop > 20 ) { + notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8; + } else { + window.impress().next(); + } + }; + + var timerReset = function() { + consoleWindow.timerStart = new Date(); + }; + + // Show a clock + var clockTick = function() { + var now = new Date(); + var hours = now.getHours(); + var minutes = now.getMinutes(); + var seconds = now.getSeconds(); + var ampm = ''; + + if ( lang.useAMPM ) { + ampm = ( hours < 12 ) ? 'AM' : 'PM'; + hours = ( hours > 12 ) ? hours - 12 : hours; + hours = ( hours === 0 ) ? 12 : hours; + } + + // Clock + var clockStr = zeroPad( hours ) + ':' + zeroPad( minutes ) + ':' + zeroPad( seconds ) + + ' ' + ampm; + consoleWindow.document.getElementById( 'clock' ).firstChild.nodeValue = clockStr; + + // Timer + seconds = Math.floor( ( now - consoleWindow.timerStart ) / 1000 ); + minutes = Math.floor( seconds / 60 ); + seconds = Math.floor( seconds % 60 ); + consoleWindow.document.getElementById( 'timer' ).firstChild.nodeValue = + zeroPad( minutes ) + 'm ' + zeroPad( seconds ) + 's'; + + if ( !consoleWindow.initialized ) { + + // Nudge the slide windows after load, or they will scrolled wrong on Firefox. + consoleWindow.document.getElementById( 'slideView' ).contentWindow.scrollTo( 0, 0 ); + consoleWindow.document.getElementById( 'preView' ).contentWindow.scrollTo( 0, 0 ); + consoleWindow.initialized = true; + } + }; + + var registerKeyEvent = function( keyCodes, handler, window ) { + if ( window === undefined ) { + window = consoleWindow; + } + + // Prevent default keydown action when one of supported key is pressed + window.document.addEventListener( 'keydown', function( event ) { + if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey && + keyCodes.indexOf( event.keyCode ) !== -1 ) { + event.preventDefault(); + } + }, false ); + + // Trigger impress action on keyup + window.document.addEventListener( 'keyup', function( event ) { + if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey && + keyCodes.indexOf( event.keyCode ) !== -1 ) { + handler(); + event.preventDefault(); + } + }, false ); + }; + + var consoleOnLoad = function() { + var slideView = consoleWindow.document.getElementById( 'slideView' ); + var preView = consoleWindow.document.getElementById( 'preView' ); + + // Firefox: + slideView.contentDocument.body.classList.add( 'impress-console' ); + preView.contentDocument.body.classList.add( 'impress-console' ); + if ( cssFileIframe !== undefined ) { + slideView.contentDocument.head.insertAdjacentHTML( + 'beforeend', + '' + ); + preView.contentDocument.head.insertAdjacentHTML( + 'beforeend', + '' + ); + } + + // Chrome: + slideView.addEventListener( 'load', function() { + slideView.contentDocument.body.classList.add( 'impress-console' ); + if ( cssFileIframe !== undefined ) { + slideView.contentDocument.head.insertAdjacentHTML( + 'beforeend', + '' + ); + } + } ); + preView.addEventListener( 'load', function() { + preView.contentDocument.body.classList.add( 'impress-console' ); + if ( cssFileIframe !== undefined ) { + preView.contentDocument.head.insertAdjacentHTML( + 'beforeend', + '' ); + } + } ); + }; + + var open = function() { + if ( top.isconsoleWindow ) { + return; + } + + if ( consoleWindow && !consoleWindow.closed ) { + consoleWindow.focus(); + } else { + consoleWindow = window.open( '', 'impressConsole' ); + + // If opening failes this may be because the browser prevents this from + // not (or less) interactive JavaScript... + if ( consoleWindow == null ) { + + // ... so I add a button to klick. + // workaround on firefox + var message = document.createElement( 'div' ); + message.id = 'impress-console-button'; + message.style.position = 'fixed'; + message.style.left = 0; + message.style.top = 0; + message.style.right = 0; + message.style.bottom = 0; + message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'; + var clickStr = 'var x = document.getElementById(\'impress-console-button\');' + + 'x.parentNode.removeChild(x);' + + 'var r = document.getElementById(\'' + rootId + '\');' + + 'impress(\'' + rootId + + '\').lib.util.triggerEvent(r, \'impress:console:open\', {})'; + var styleStr = 'margin: 25vh 25vw;width:50vw;height:50vh;'; + message.innerHTML = ''; + document.body.appendChild( message ); + return; + } + + var cssLink = ''; + if ( cssFile !== undefined ) { + cssLink = ''; + } + + // This sets the window location to the main window location, so css can be loaded: + consoleWindow.document.open(); + + // Write the template: + consoleWindow.document.write( + + // CssStyleStr is lots of inline defined at the end of this file + consoleTemplate.replace( '{{cssStyle}}', cssStyleStr() ) + .replace( '{{cssLink}}', cssLink ) + .replace( /{{.*?}}/gi, function( x ) { + return lang[ x.substring( 2, x.length - 2 ) ]; } + ) + ); + consoleWindow.document.title = 'Speaker Console (' + document.title + ')'; + consoleWindow.impress = window.impress; + + // We set this flag so we can detect it later, to prevent infinite popups. + consoleWindow.isconsoleWindow = true; + + // Set the onload function: + consoleWindow.onload = consoleOnLoad; + + // Add clock tick + consoleWindow.timerStart = new Date(); + consoleWindow.timerReset = timerReset; + consoleWindow.clockInterval = setInterval( allConsoles[ rootId ].clockTick, 1000 ); + + // Keyboard navigation handlers + // 33: pg up, 37: left, 38: up + registerKeyEvent( [ 33, 37, 38 ], window.impress().prev ); + + // 34: pg down, 39: right, 40: down + registerKeyEvent( [ 34, 39, 40 ], window.impress().next ); + + // 32: space + registerKeyEvent( [ 32 ], spaceHandler ); + + // 82: R + registerKeyEvent( [ 82 ], timerReset ); + + // Cleanup + consoleWindow.onbeforeunload = function() { + + // I don't know why onunload doesn't work here. + clearInterval( consoleWindow.clockInterval ); + }; + + // It will need a little nudge on Firefox, but only after loading: + onStepEnter(); + consoleWindow.initialized = false; + consoleWindow.document.close(); + + //Catch any window resize to pass size on + window.onresize = resize; + consoleWindow.onresize = resize; + + return consoleWindow; + } + }; + + var resize = function() { + var slideView = consoleWindow.document.getElementById( 'slideView' ); + var preView = consoleWindow.document.getElementById( 'preView' ); + + // Get ratio of presentation + var ratio = window.innerHeight / window.innerWidth; + + // Get size available for views + var views = consoleWindow.document.getElementById( 'views' ); + + // SlideView may have a border or some padding: + // asuming same border width on both direktions + var delta = slideView.offsetWidth - slideView.clientWidth; + + // Set views + var slideViewWidth = ( views.clientWidth - delta ); + var slideViewHeight = Math.floor( slideViewWidth * ratio ); + + var preViewTop = slideViewHeight + preViewGap; + + var preViewWidth = Math.floor( slideViewWidth * preViewDefaultFactor ); + var preViewHeight = Math.floor( slideViewHeight * preViewDefaultFactor ); + + // Shrink preview to fit into space available + if ( views.clientHeight - delta < preViewTop + preViewHeight ) { + preViewHeight = views.clientHeight - delta - preViewTop; + preViewWidth = Math.floor( preViewHeight / ratio ); + } + + // If preview is not high enough forget ratios! + if ( preViewWidth <= Math.floor( slideViewWidth * preViewMinimumFactor ) ) { + slideViewWidth = ( views.clientWidth - delta ); + slideViewHeight = Math.floor( ( views.clientHeight - delta - preViewGap ) / + ( 1 + preViewMinimumFactor ) ); + + preViewTop = slideViewHeight + preViewGap; + + preViewWidth = Math.floor( slideViewWidth * preViewMinimumFactor ); + preViewHeight = views.clientHeight - delta - preViewTop; + } + + // Set the calculated into styles + slideView.style.width = slideViewWidth + 'px'; + slideView.style.height = slideViewHeight + 'px'; + + preView.style.top = preViewTop + 'px'; + + preView.style.width = preViewWidth + 'px'; + preView.style.height = preViewHeight + 'px'; + }; + + var _init = function( cssConsole, cssIframe ) { + if ( cssConsole !== undefined ) { + cssFile = cssConsole; + } + + // You can also specify the css in the presentation root div: + //
+ else if ( root.dataset.consoleCss !== undefined ) { + cssFile = root.dataset.consoleCss; + } + + if ( cssIframe !== undefined ) { + cssFileIframe = cssIframe; + } else if ( root.dataset.consoleCssIframe !== undefined ) { + cssFileIframe = root.dataset.consoleCssIframe; + } + + // Register the event + root.addEventListener( 'impress:stepleave', onStepLeave ); + root.addEventListener( 'impress:stepenter', onStepEnter ); + root.addEventListener( 'impress:substep:stepleaveaborted', onSubstep ); + root.addEventListener( 'impress:substep:show', onSubstepShow ); + root.addEventListener( 'impress:substep:hide', onSubstepHide ); + + //When the window closes, clean up after ourselves. + window.onunload = function() { + if ( consoleWindow && !consoleWindow.closed ) { + consoleWindow.close(); + } + }; + + //Open speaker console when they press 'p' + registerKeyEvent( [ 80 ], open, window ); + + //Btw, you can also launch console automatically: + //
+ if ( root.dataset.consoleAutolaunch === 'true' ) { + open(); + } + }; + + var init = function( cssConsole, cssIframe ) { + if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) && + ( cssIframe === undefined || cssIframe === cssFileIframeOldDefault ) ) { + window.console.log( 'impressConsole().init() is deprecated. ' + + 'impressConsole is now initialized automatically when you ' + + 'call impress().init().' ); + } + _init( cssConsole, cssIframe ); + }; + + // New API for impress.js plugins is based on using events + root.addEventListener( 'impress:console:open', function() { + open(); + } ); + + /** + * Register a key code to an event handler + * + * :param: event.detail.keyCodes List of key codes + * :param: event.detail.handler A function registered as the event handler + * :param: event.detail.window The console window to register the keycode in + */ + root.addEventListener( 'impress:console:registerKeyEvent', function( event ) { + registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window ); + } ); + + // Return the object + allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick, + registerKeyEvent: registerKeyEvent, _init: _init }; + return allConsoles[ rootId ]; + + }; + + // This initializes impressConsole automatically when initializing impress itself + document.addEventListener( 'impress:init', function( event ) { + + // Note: impressConsole wants the id string, not the DOM element directly + impressConsole( event.target.id )._init(); + + // Add 'P' to the help popup + triggerEvent( document, 'impress:help:add', + { command: 'P', text: 'Presenter console', row: 10 } ); + } ); + + // Returns a string to be used inline as a css `; + }; + +} )( document, window ); + +/** + * Media Plugin + * + * This plugin will do the following things: + * + * - Add a special class when playing (body.impress-media-video-playing + * and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused + * and body.impress-media-audio-paused) (removing them when ending). + * This can be useful for example for darkening the background or fading out other elements + * while a video is playing. + * Only media at the current step are taken into account. All classes are removed when leaving + * a step. + * + * - Introduce the following new data attributes: + * + * - data-media-autoplay="true": Autostart media when entering its step. + * - data-media-autostop="true": Stop media (= pause and reset to start), when leaving its + * step. + * - data-media-autopause="true": Pause media but keep current time when leaving its step. + * + * When these attributes are added to a step they are inherited by all media on this step. + * Of course this setting can be overwritten by adding different attributes to inidvidual + * media. + * + * The same rule applies when this attributes is added to the root element. Settings can be + * overwritten for individual steps and media. + * + * Examples: + * - data-media-autostart="true" data-media-autostop="true": start media on enter, stop on + * leave, restart from beginning when re-entering the step. + * + * - data-media-autostart="true" data-media-autopause="true": start media on enter, pause on + * leave, resume on re-enter + * + * - data-media-autostart="true" data-media-autostop="true" data-media-autopause="true": start + * media on enter, stop on leave (stop overwrites pause). + * + * - data-media-autostart="true" data-media-autopause="false": let media start automatically + * when entering a step and let it play when leaving the step. + * + * -
...
+ * All media is startet automatically on all steps except the one that has the + * data-media-autostart="false" attribute. + * + * - Pro tip: Use