MOBILE-2235 h5p: Include core H5P libraries and assets
parent
93259097d6
commit
ef5f96a643
|
@ -41,4 +41,8 @@ module.exports = {
|
|||
src: ['{{ROOT}}/node_modules/mathjax/localization/**/*'],
|
||||
dest: '{{WWW}}/lib/mathjax/localization'
|
||||
},
|
||||
copyH5P: {
|
||||
src: ['{{ROOT}}/src/core/h5p/assets/**/*'],
|
||||
dest: '{{WWW}}/h5p/'
|
||||
},
|
||||
};
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMid meet">
|
||||
<metadata>
|
||||
<json>
|
||||
<![CDATA[
|
||||
{
|
||||
"fontFamily": "h5p-core-21",
|
||||
"description": "Font generated by IcoMoon.",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 1,
|
||||
"version": "Version 1.1",
|
||||
"fontId": "h5p-core-21",
|
||||
"psName": "h5p-core-21",
|
||||
"subFamily": "Regular",
|
||||
"fullName": "h5p-core-21"
|
||||
}
|
||||
]]>
|
||||
</json>
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="h5p-core-21" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="arrow-down" data-tags="arrow-down" d="M234 389.669h556l-278-278z" />
|
||||
<glyph unicode="" glyph-name="arrow-left" data-tags="arrow-left" d="M381-11.331v524l262-262z" />
|
||||
<glyph unicode="" glyph-name="colapse" data-tags="colapse" d="M512 447.336l256-256-60-60-196 196-196-196-60 60z" />
|
||||
<glyph unicode="" glyph-name="expand" data-tags="expand" d="M708 423.336l60-60-256-256-256 256 60 60 196-196z" />
|
||||
<glyph unicode="" glyph-name="move" data-tags="move" d="M386.662 725.063h71.27v-71.27h-71.27v71.27zM566.067 725.063h71.27v-71.27h-71.27v71.27zM386.662 568.8h71.27v-71.27h-71.27v71.27zM566.067 568.8h71.27v-71.27h-71.27v71.27zM386.662 412.435h71.27v-71.27h-71.27v71.27zM566.067 412.435h71.27v-71.27h-71.27v71.27zM386.662 256.173h71.27v-71.27h-71.27v71.27zM566.067 256.173h71.27v-71.27h-71.27v71.27zM386.662 99.808h71.27v-71.27h-71.27v71.27zM566.067 99.808h71.27v-71.27h-71.27v71.27zM386.662-56.454h71.27v-71.27h-71.27v71.27zM566.067-56.454h71.27v-71.27h-71.27v71.27z" />
|
||||
<glyph unicode="" glyph-name="check-mark" data-tags="check-mark" d="M454.299 245.924l-116.917 116.917-84.781-84.707 201.696-201.697 317.097 317.097-84.781 84.706z" />
|
||||
<glyph unicode="" glyph-name="arrow-up-circle" data-tags="arrow-up-circle" d="M512 606.057c-148.616 0-264.722-120.75-260.077-269.367 0-125.395 88.241-232.212 208.991-255.434v213.636h-92.885c-13.933 0-13.933 9.288-9.288 18.577l139.327 171.838c4.645 9.288 13.933 9.288 23.221 4.645 0 0 4.645-4.645 4.645-4.645l139.327-171.838c9.288-9.288 4.645-18.577-9.288-18.577h-92.885v-213.636c143.972 32.51 232.212 171.838 199.703 315.808-23.221 120.75-130.039 204.347-250.789 208.991z" />
|
||||
<glyph unicode="" glyph-name="info-circle" data-tags="info-circle" d="M512 601.601c-144.077 0-260.266-116.191-260.266-260.266s116.191-260.266 260.266-260.266 260.266 116.191 260.266 260.266v0c0 139.429-116.191 255.619-260.266 260.266zM470.171 550.478h88.305v-69.714h-88.305v69.714zM600.305 160.078h-181.257v51.123h51.123v162.666h-51.123v51.123h139.429v-218.438h46.477l-4.648-46.477z" />
|
||||
<glyph unicode="" glyph-name="search" data-tags="search" d="M772.098 125.51l-110.68 110.68c71.943 99.612 49.806 243.494-49.806 315.437s-243.494 44.27-315.437-55.339c-71.943-99.612-49.806-243.494 49.806-315.437 77.475-55.339 182.623-55.339 260.098 0l110.68-110.68c5.533-5.533 11.068-5.533 16.601 0 0 0 0 0 0 0l33.205 33.205c11.068 5.533 11.068 16.601 5.533 22.137 0 0 0 0 0 0zM478.795 202.985c-88.544 0-160.486 71.943-160.486 160.486s71.943 160.486 160.486 160.486 160.486-71.943 160.486-160.486-71.943-160.486-160.486-160.486v0z" />
|
||||
<glyph unicode="" glyph-name="fullscreen" data-tags="fullscreen" d="M368.55 490.521c5.737 5.737 0 5.737-5.737 5.737l-103.284 11.476c-5.737 5.737-11.476 0-11.476-5.737l11.476-109.021c0-5.737 5.737-5.737 5.737-5.737l103.284 103.284zM293.959 427.403l63.118-63.118c5.737-5.737 11.476-5.737 17.213 0l22.953 22.953c5.737 5.737 5.737 11.476 0 17.213l-63.118 57.379-40.166-34.429zM787.42 387.237c5.737-5.737 5.737 0 5.737 5.737l11.476 109.021c0 5.737-5.737 11.476-11.476 11.476l-109.021-11.476c-5.737 0-5.737-5.737-5.737-5.737l109.021-109.021zM724.305 461.832l-63.118-63.118c-5.737-5.737-5.737-11.476 0-17.213l22.953-22.953c5.737-5.737 11.476-5.737 17.213 0l63.118 63.118-40.166 40.166zM689.876 180.672c-5.737-5.737 0-5.737 5.737-5.737l109.021-11.476c5.737 0 11.476 5.737 11.476 11.476l-17.213 103.284c0 5.737-5.737 5.737-5.737 5.737l-103.284-103.284zM758.731 249.527l-63.118 63.118c-5.737 5.737-11.476 5.737-17.213 0l-22.953-22.953c-5.737-5.737-5.737-11.476 0-17.213l63.118-63.118 40.166 40.166zM265.269 283.956c-5.737 5.737-5.737 0-5.737-5.737l-11.476-109.021c0-5.737 5.737-11.476 11.476-11.476l109.021 11.476c5.737 0 5.737 5.737 5.737 5.737l-109.021 109.021zM334.124 209.362l63.118 63.118c5.737 5.737 5.737 11.476 0 17.213l-22.953 22.953c-5.737 5.737-11.476 5.737-17.213 0l-63.118-63.118 40.166-40.166zM161.985 593.805v-499.201h722.979v499.201h-722.979zM844.799 134.77h-636.911v413.13h636.911v-413.13z" />
|
||||
<glyph unicode="" glyph-name="h5p" data-tags="h5p" d="M934.072 489.192c-22.319 16.738-50.216 27.897-89.273 27.897h-139.487v-66.954h-156.225l-11.159-55.795c11.159 5.579 27.897 11.159 39.057 11.159s22.319 0 33.476 0c33.476 0 66.954-11.159 89.273-33.476s33.476-50.216 33.476-83.692c0-22.319-5.579-44.635-16.738-66.954s-27.897-39.057-50.216-50.216c-5.579-5.579-16.738 0-22.319-11.159h117.17v133.908h66.954c44.635 0 78.113 11.159 100.43 27.897 22.319 22.319 33.476 50.216 33.476 83.692 0 39.057-11.159 66.954-27.897 83.692v0zM839.221 377.603c-11.159-5.579-22.319-11.159-44.635-11.159h-33.476v83.692h39.057c22.319 0 33.476-5.579 44.635-11.159 5.579-5.579 11.159-16.738 11.159-27.897 0-16.738-5.579-27.897-16.738-33.476v0zM565.826 338.546c-16.738 0-33.476-11.159-44.635-27.897l-94.851 16.738 44.635 195.281h-94.851v-150.646h-117.17v150.646h-111.589v-362.667h111.589v133.908h117.17v-133.908h139.487c-16.738 11.159-33.476 11.159-44.635 22.319s-22.319 22.319-27.897 33.476c-5.579 11.159-11.159 22.319-16.738 39.057l94.851 16.738c5.579-16.738 22.319-27.897 44.635-27.897 27.897 0 50.216 22.319 50.216 50.216 0 22.319-22.319 44.635-50.216 44.635v0z" />
|
||||
<glyph unicode="" glyph-name="rights-of-use" data-tags="rights-of-use" d="M899.611 329.519c0-5.907 0-5.907 0-5.907-23.631-23.631-47.261-35.448-76.799-41.355-11.813 0-23.631-5.907-35.448-5.907s-17.724 0-29.537 0c0 0-5.907 0-5.907 5.907-64.985 59.079-135.877 118.153-200.863 183.139 0 0-5.907 0-5.907 0-23.631-5.907-47.261-11.813-70.892-17.724s-53.168 0-76.799 11.813c-17.724 11.813-23.631 17.724-29.537 35.448-5.907 5.907 0 23.631 11.813 23.631 41.355 11.813 88.616 29.537 129.971 47.261 11.813 5.907 29.537 5.907 41.355 5.907 5.907 0 11.813-5.907 11.813-5.907 41.355-17.724 82.709-29.537 124.060-47.261 0 0 5.907 0 5.907 0 29.537 5.907 64.985 17.724 94.523 23.631 5.907 0 5.907 0 5.907 0l106.34-212.676zM291.12 335.429c17.724 11.813 35.448 5.907 53.168-11.813 11.813-11.813 11.813-29.537 5.907-47.261 17.724 5.907 35.448-5.907 41.355-17.724 11.813-17.724 5.907-35.448-5.907-47.261 5.907 0 11.813 0 17.724 0 11.813-5.907 23.631-11.813 29.537-29.537s0-29.537-5.907-35.448c-5.907-5.907-11.813-11.813-17.724-17.724s-11.813-11.813-17.724-17.724-35.448-17.724-53.168 0c-29.537 29.537-53.168 64.985-82.709 94.523-17.724 23.631-35.448 41.355-47.261 64.985-5.907 11.813-11.813 17.724-11.813 29.537 0 5.907 0 17.724 5.907 23.631 11.813 11.813 17.724 17.724 29.537 29.537 17.724 17.724 47.261 11.813 64.985-5.907-5.907 0-5.907-5.907-5.907-11.813v0zM438.811 128.66l29.537-29.537c17.724-17.724 47.261-11.813 59.079 5.907l-5.907 5.907c-23.631 23.631-47.261 47.261-70.892 70.892-5.907 5.907-5.907 5.907-5.907 11.813s5.907 5.907 11.813 11.813c5.907 0 11.813 0 11.813-5.907 11.813-11.813 29.537-29.537 47.261-47.261 11.813-11.813 29.537-29.537 47.261-47.261 5.907-11.813 17.724-11.813 29.537-11.813 11.813 5.907 23.631 11.813 29.537 23.631 0 5.907 0 5.907 0 5.907-41.355 41.355-88.616 82.709-129.971 129.971-5.907 5.907-5.907 5.907-5.907 11.813 0 11.813 11.813 11.813 23.631 5.907 0 0 5.907 0 5.907-5.907 41.355-41.355 88.616-88.616 129.971-129.971 5.907-5.907 5.907-5.907 5.907-5.907 17.724 0 35.448 17.724 35.448 35.448 0 5.907 0 5.907 0 5.907-47.261 47.261-100.429 100.429-147.691 147.691-5.907 5.907-5.907 5.907-5.907 11.813s5.907 11.813 5.907 11.813c5.907 0 11.813 0 11.813-5.907 5.907-5.907 5.907-5.907 11.813-11.813 35.448-35.448 70.892-70.892 106.34-106.34 11.813-11.813 23.631-23.631 29.537-29.537 0 0 5.907-5.907 5.907 0 23.631 5.907 35.448 29.537 29.537 53.168h35.448c0 0 0 0 0 0 0-5.907 0-17.724 0-23.631-5.907-29.537-23.631-47.261-53.168-59.079 0 0-5.907 0-5.907-5.907-11.813-29.537-35.448-53.168-64.985-53.168-5.907 0-5.907 0-5.907-5.907-17.724-35.448-59.079-47.261-88.616-35.448-5.907 0-11.813 5.907-11.813 5.907-5.907-5.907-11.813-11.813-23.631-17.724-29.537-11.813-59.079-5.907-76.799 11.813-11.813 11.813-17.724 17.724-29.537 29.537 17.724 23.631 23.631 29.537 29.537 41.355v0 0zM273.396 642.628c29.537-11.813 64.985-23.631 94.523-29.537 35.448-11.813 64.985-23.631 100.429-29.537 0 0 0 0 5.907 0-17.724-5.907-35.448-11.813-47.261-17.724 0 0-5.907 0-5.907 0-47.261 11.813-94.523 23.631-135.877 41.355-5.907 0-5.907 0-5.907 0l-76.799-183.139c0-11.813 5.907-17.724 11.813-23.631s5.907-5.907 5.907-11.813c-5.907-5.907-11.813-17.724-23.631-23.631-17.724 17.724-29.537 35.448-29.537 64.985l88.616 212.676c-5.907-11.813 5.907 5.907 17.724 0v0z" />
|
||||
<glyph unicode="" glyph-name="delete-circle" data-tags="delete-circle" d="M512 601.601c-147.107 0-260.266-118.817-260.266-260.266s118.817-260.266 260.266-260.266 260.266 118.817 260.266 260.266-113.158 260.266-260.266 260.266zM653.449 262.123c5.659-5.659 5.659-16.973 0-28.29l-33.949-33.949c-5.659-5.659-16.973-5.659-28.29 0l-79.212 79.212-79.212-79.212c-5.659-5.659-16.973-5.659-28.29 0l-33.949 33.949c-5.659 5.659-5.659 16.973 0 28.29l84.871 79.212-79.212 79.212c-5.659 5.659-5.659 16.973 0 28.29l33.949 33.949c5.659 5.659 16.973 5.659 28.29 0l73.554-84.871 79.212 79.212c5.659 5.659 16.973 5.659 28.29 0l33.949-33.949c5.659-5.659 5.659-16.973 0-28.29l-79.212-73.554 79.212-79.212z" />
|
||||
<glyph unicode="" glyph-name="window" data-tags="window" d="M203.936 461.136c-5.704-5.704 0-5.704 5.704-5.704l108.394-11.41c5.704 0 11.41 5.704 11.41 11.41l-17.114 102.687c0 5.704-5.704 5.704-5.704 5.704l-102.687-102.687zM272.395 523.891l-62.752 62.752c-5.704 5.704-11.41 5.704-17.114 0l-17.114-22.821c-5.704-5.704-5.704-11.41 0-17.114l62.752-62.752 34.228 39.935zM751.605 558.119c-5.704 5.704-5.704 0-5.704-5.704l-11.41-108.394c0-5.704 5.704-11.41 11.41-11.41l108.394 11.41c5.704 0 5.704 5.704 5.704 5.704l-108.394 108.394zM814.357 483.957l62.752 62.752c5.704 5.704 5.704 11.41 0 17.114l-22.821 22.821c-5.704 5.704-11.41 5.704-17.114 0l-62.752-62.752 39.935-39.935zM848.588 221.534c5.704 5.704 0 5.704-5.704 5.704l-102.687 17.114c-5.704 0-11.41-5.704-11.41-11.41l11.41-108.394c0-5.704 5.704-5.704 5.704-5.704l102.687 102.687zM780.129 158.779l62.752-62.752c5.704-5.704 11.41-5.704 17.114 0l22.821 22.821c5.704 5.704 5.704 11.41 0 17.114l-62.752 62.752-39.935-39.935zM300.919 124.551c5.704-5.704 5.704 0 5.704 5.704l11.41 108.394c0 5.704-5.704 11.41-11.41 11.41l-108.394-11.41c-5.704 0-5.704-5.704-5.704-5.704l108.394-108.394zM238.167 193.010l-62.752-62.752c-5.704-5.704-5.704-11.41 0-17.114l22.821-22.821c5.704-5.704 11.41-5.704 17.114 0l62.752 62.752-39.935 39.935zM352.264 466.843v-239.605h347.998v239.605h-347.998zM654.622 267.172h-262.424v154.032h262.424v-154.032z" />
|
||||
<glyph unicode="" glyph-name="code" data-tags="code" d="M449.641 235.325c6.235-6.235 6.235-12.472 6.235-18.707v-62.359c0-6.235-6.235-6.235-6.235-6.235l-230.728 155.897c-6.235 6.235-6.235 12.472-6.235 18.707v49.886c0 6.235 6.235 12.472 6.235 18.707l230.728 155.897c6.235 6.235 6.235 0 6.235-6.235v-62.359c0-6.235-6.235-12.472-6.235-18.707l-162.134-112.245c-6.235-6.235-6.235-6.235 0-12.472l162.134-99.776zM736.493 341.335c6.235 6.235 6.235 6.235 0 12.472l-155.897 112.245c-6.235 6.235-6.235 12.472-6.235 18.707v62.359c0 6.235 6.235 6.235 6.235 6.235l230.728-155.897c6.235-6.235 6.235-12.472 6.235-18.707v-49.886c0-6.235-6.235-12.472-6.235-18.707l-230.728-155.897c-6.235-6.235-6.235 0-6.235 6.235v62.359c0 6.235 6.235 12.472 6.235 18.707l155.897 99.776z" />
|
||||
<glyph unicode="" glyph-name="download" data-tags="download" d="M358.941 435.525c-11.773 0-17.66-5.887-5.887-17.66l153.059-188.382c5.887-11.773 23.547-11.773 29.433 0l153.059 188.382c5.887 11.773 5.887 17.66-5.887 17.66h-323.782zM576.756 423.751v135.399c0 11.773-11.773 23.547-23.547 23.547h-70.643c-11.773 0-23.547-11.773-23.547-23.547v-141.286h117.739zM653.286 288.352c-5.887 0-17.66-5.887-23.547-11.773l-76.53-94.19c-5.887-5.887-17.66-17.66-23.547-23.547 0 0-5.887-5.887-11.773-5.887s-17.66 11.773-17.66 11.773c-5.887 5.887-17.66 17.66-23.547 23.547l-76.53 94.19c-5.887 5.887-17.66 11.773-23.547 11.773h-123.626c-5.887 0-17.66-5.887-17.66-17.66v-141.286c0-5.887 5.887-17.66 17.66-17.66h529.824c5.887 0 17.66 5.887 17.66 17.66v141.286c0 5.887-5.887 17.66-17.66 17.66l-129.513-5.887zM305.958 176.502c-17.66 0-29.433 11.773-29.433 29.433s11.773 29.433 29.433 29.433c17.66 0 29.433-11.773 29.433-29.433s-11.773-29.433-29.433-29.433v0z" />
|
||||
<glyph unicode="" glyph-name="delete" data-tags="delete" d="M620.266 341.335l134.045 134.045c10.311 10.311 10.311 30.934 0 41.245l-61.866 61.866c-10.311 10.311-30.934 10.311-41.245 0l-134.045-134.045-134.045 134.045c-10.311 10.311-30.934 10.311-41.245 0l-61.866-61.866c-10.311-10.311-10.311-30.934 0-41.245l134.045-134.045-134.045-134.045c-10.311-10.311-10.311-30.934 0-41.245l61.866-61.866c10.311-10.311 30.934-10.311 41.245 0l134.045 134.045 134.045-134.045c10.311-10.311 30.934-10.311 41.245 0l61.866 61.866c10.311 10.311 10.311 30.934 0 41.245l-134.045 134.045z" />
|
||||
<glyph unicode="" glyph-name="edit-image" data-tags="edit-image" d="M300.237 621.639c69.018 23.142 133.325 14.234 189.133-33.28 56.627-48.128 77.619-110.592 63.181-183.808-2.355-12.186 0.307-19.456 8.704-27.853 93.901-93.389 156.774-156.467 250.47-250.163 5.427-5.427 10.957-10.854 15.667-16.896 39.424-50.278 16.794-124.006-44.237-142.029-36.966-10.957-68.403 0-95.334 27.034-95.642 96.051-160.973 160.973-256.614 257.024-6.963 6.963-12.8 8.909-22.63 6.758-117.76-26.317-229.171 60.826-231.731 181.453-0.614 26.419 3.584 52.326 15.974 77.926 34.816-34.816 68.506-67.789 101.274-101.786 10.445-10.752 20.992-15.36 36.045-15.36 14.643 0 25.19 3.891 34.816 14.848 10.752 12.39 23.040 23.347 34.611 35.021 14.336 14.438 14.336 46.080-0.205 60.518-35.123 35.226-70.349 70.349-106.598 106.496 3.891 2.253 5.632 3.482 7.475 4.096zM703.386 63.559c-0.41-24.269 20.685-45.466 44.851-45.158 23.757 0.41 44.032 20.992 43.93 44.544-0.102 23.757-20.275 44.032-44.237 44.134-23.859 0.307-44.237-19.661-44.544-43.52z" />
|
||||
<glyph unicode="" glyph-name="hourglass" data-tags="hourglass" d="M733.286-10.579c-147.763 0-295.526 0-443.29 0 0 2.048 0.102 4.096 0 6.144-0.307 13.824-1.024 32.666-0.922 46.49 0.41 39.731 6.861 78.131 19.046 115.2 17.203 52.224 43.725 96.256 81.306 130.355 4.506 4.096 9.216 7.885 13.722 11.776-0.205 0.717-0.307 1.126-0.41 1.229-1.331 1.229-2.765 2.355-4.198 3.584-28.058 22.63-50.688 51.405-68.403 85.606-30.618 59.085-43.52 123.597-41.165 192.614 0.205 7.168 0.614 18.33 0.922 25.498 147.763 0 295.526 0 443.29 0 0.205-1.331 0.512-2.662 0.614-3.994 2.662-36.966 1.229-77.722-5.939-113.869-14.336-72.909-44.544-133.837-95.027-179.405-4.096-3.686-8.294-7.066-12.39-10.547 0.205-0.717 0.307-1.126 0.512-1.331 0.819-0.717 1.638-1.434 2.458-2.15 42.189-33.894 71.68-79.872 90.931-135.782 11.776-34.202 18.637-69.837 20.070-106.701 0.819-19.763-0.614-44.851-1.126-64.717zM687.309 32.634c0 6.554 0.205 12.493 0 18.432-1.331 37.581-7.27 74.138-19.866 108.749-17.92 49.562-45.568 88.269-88.678 108.646-2.458 1.126-2.97 3.072-2.97 5.837 0.102 16.691 0.102 33.485 0 50.176 0 3.994 1.331 5.427 4.096 6.963 9.114 5.325 18.432 10.24 26.829 16.896 29.696 23.552 49.152 56.934 62.362 95.744 10.342 30.413 15.77 62.259 17.818 94.822 0.614 9.114 0.102 18.227 0.102 27.546-116.634 0-233.574 0-351.027 0 0.307-8.704 0.614-16.998 1.024-25.395 1.946-37.274 8.499-73.216 21.504-107.213 18.125-47.104 45.261-83.558 86.528-103.117 2.253-1.024 2.867-2.662 2.867-5.427-0.102-17.203-0.102-34.509 0-51.712 0-3.072-1.024-4.506-3.277-5.632-5.632-2.867-11.366-5.734-16.691-9.216-34.304-22.733-56.832-57.754-71.987-100.147-12.493-35.123-18.125-71.987-19.661-109.875-0.205-5.325 0-10.752 0-16.282 117.35 0.205 234.189 0.205 351.027 0.205zM410.214 451.962c68.096 0 135.373 0 203.674 0-3.789-6.554-7.168-12.595-10.752-18.227-10.957-16.998-24.269-30.618-39.731-41.472s-27.75-25.293-32.768-46.592c-1.638-6.963-2.765-14.336-2.867-21.606-0.307-17.203-0.205-34.406 0.307-51.712 0.717-28.058 12.493-48.947 32.154-62.566 43.008-30.003 65.843-75.878 75.776-132.506 0.307-1.638 0.307-3.379 0.614-5.53-83.149 0-166.093 0-249.242 0 2.662 20.685 7.885 40.346 15.462 59.085 13.312 32.973 32.768 59.187 59.494 77.722 16.589 11.469 28.365 27.955 32.358 50.995 0.819 4.813 1.331 9.83 1.434 14.746 0.102 18.637 0.614 37.274-0.205 55.808-1.126 24.678-11.981 43.213-28.57 57.139-9.216 7.782-18.944 14.746-27.648 23.142-11.981 11.162-21.197 25.395-29.491 41.574z" />
|
||||
<glyph unicode="" glyph-name="plus-icon" data-tags="plus-icon" d="M768 285.015c0-19.323-15.664-34.987-34.987-34.987h-151.040v-151.467c0-19.323-15.664-34.987-34.987-34.987h-69.547c-19.323 0-34.987 15.664-34.987 34.987v151.467h-151.467c-19.323 0-34.987 15.664-34.987 34.987v69.547c0 19.323 15.664 34.987 34.987 34.987h151.467v151.467c0 19.323 15.664 34.987 34.987 34.987h69.547c19.323 0 34.987-15.664 34.987-34.987v-151.467h151.467c19.323 0 34.987-15.664 34.987-34.987z" />
|
||||
<glyph unicode="" glyph-name="video-upload-icon" data-tags="video-upload-icon" d="M384 328.535v-128c0-21.333 21.333-42.667 42.667-42.667h128c21.333 0 42.667 17.067 42.667 42.667v128c0 21.333-17.067 42.667-42.667 42.667h-128c-21.333 0-42.667-17.067-42.667-42.667zM785.067 499.201l-102.4 106.667c-12.8 12.8-38.4 21.333-55.467 21.333h-140.8l21.333-42.667h89.6v-136.533c0-17.067 12.8-29.867 29.867-29.867h140.8v-341.333h-426.667v328.533h-42.667v-341.333c0-17.067 12.8-29.867 29.867-29.867h448c17.067 0 29.867 12.8 29.867 29.867v384c4.267 17.067-8.533 38.4-21.333 51.2zM640 456.535v123.733c4.267 0 12.8-4.267 12.8-8.533l102.4-102.4c4.267-4.267 4.267-8.533 8.533-12.8h-123.733zM725.333 166.401v196.267c0 4.267-4.267 8.533-8.533 8.533s-8.533 0-12.8-4.267l-89.6-89.6v-29.867l89.6-89.6c8.533-4.267 8.533 0 12.8 0 4.267 4.267 8.533 4.267 8.533 8.533zM349.867 473.601v136.533l59.733-59.733c8.533-8.533 17.067-4.267 25.6 0l12.8 12.8c8.533 8.533 8.533 17.067 0 25.6l-115.2 106.667c-8.533 8.533-17.067 8.533-21.333 0l-115.2-110.933c-4.267-8.533-4.267-17.067 0-25.6l12.8-12.8c8.533-8.533 17.067-8.533 21.333 0l59.733 59.733v-136.533c0-8.533 8.533-17.067 17.067-17.067h25.6c0 0 17.067 8.533 17.067 21.333z" />
|
||||
<glyph unicode="" glyph-name="play-icon" data-tags="play-icon" d="M392.533 597.335c81.067 46.933 187.733 46.933 273.067 0 42.667-25.6 72.533-55.467 98.133-98.133 72.533-128 29.867-294.4-98.133-371.2-128-72.533-294.4-29.867-371.2 98.133-46.933 81.067-46.933 187.733 0 273.067 21.333 42.667 55.467 72.533 98.133 98.133zM661.333 345.601c12.8 8.533 12.8 29.867 0 38.4l-192 110.933c-8.533 4.267-12.8 4.267-21.333 0s-12.8-12.8-12.8-21.333v-226.133c0-8.533 4.267-17.067 12.8-21.333s17.067-4.267 21.333 0l192 119.467z" />
|
||||
<glyph unicode="" glyph-name="copy" data-tags="copy" d="M722.133 561.201h-247.733c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v288.533c-0.267 65.867-53.467 119.2-119.333 119.2zM778.533 156.534c0-31.333-25.067-56.4-56.4-56.4h-247.867c-31.333 0-56.4 25.067-56.4 56.4v285.467c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-285.467zM245.2 326.001v288.533c0 31.333 25.067 56.4 56.4 56.4h247.733c31.333 0 56.4-25.067 56.4-56.4v-18.8h62.667v18.8c0 65.867-53.333 119.2-119.2 119.2h-247.467c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h18.8v62.667h-18.8c-31.467-3.067-56.533 22-56.533 56.533zM681.2 360.534h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 266.401h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333zM681.2 172.268h-163.067c-18.8 0-31.333 12.533-31.333 31.333s12.533 31.333 31.333 31.333h163.067c18.8 0 31.333-12.533 31.333-31.333s-15.6-31.333-31.333-31.333z" />
|
||||
<glyph unicode="" glyph-name="examples-icon" data-tags="examples-icon" d="M213.333 166.402c89.6 38.4 183.467 68.267 273.067 17.067v281.6c-68.267 46.933-157.867 55.467-234.667 12.8l-38.4-311.467zM810.667 166.402l-42.667 315.733c-72.533 38.4-166.4 34.133-234.667-17.067v-285.867c93.867 51.2 187.733 21.333 277.333-12.8zM832 520.535c-51.2 29.867-110.933 46.933-170.667 55.467-51.2 0-102.4-8.533-149.333-29.867-46.933 21.333-98.133 29.867-149.333 29.867-59.733-4.267-119.467-25.6-170.667-55.467l-64-452.267c0 0 29.867-17.067 110.933 21.333 46.933 25.6 102.4 34.133 157.867 25.6 42.667-4.267 85.333-21.333 115.2-55.467v0c29.867 34.133 72.533 51.2 115.2 55.467 55.467 4.267 106.667-4.267 157.867-29.867 81.067-38.4 110.933-21.333 110.933-21.333l-64 456.533zM793.6 115.202c-42.667 21.333-89.6 34.133-140.8 34.133-8.533 0-21.333 0-29.867 0-42.667-4.267-81.067-17.067-115.2-42.667-34.133 25.6-72.533 38.4-115.2 42.667-12.8 0-21.333 0-34.133 0-46.933 0-93.867-8.533-136.533-29.867-21.333-12.8-46.933-21.333-72.533-25.6l64 413.867c46.933 25.6 98.133 38.4 149.333 42.667 46.933 0 93.867-8.533 136.533-25.6l12.8-8.533 12.8 4.267c42.667 17.067 89.6 25.6 136.533 25.6 51.2-4.267 102.4-17.067 149.333-42.667l59.733-418.133c-29.867 8.533-51.2 17.067-76.8 29.867z" />
|
||||
<glyph unicode="" glyph-name="tutorials-icon" data-tags="tutorials-icon" d="M887.467 430.935l-375.467-110.933h-4.267l-217.6 68.267c-21.333-25.6-34.133-59.733-34.133-98.133 21.333-12.8 25.6-38.4 12.8-59.733-4.267-4.267-8.533-8.533-12.8-12.8l17.067-145.067c0-4.267 0-4.267-4.267-8.533 0 0 0 0-4.267 0h-64c-4.267 0-4.267 0-8.533 4.267 0 4.267-4.267 4.267-4.267 8.533l17.067 145.067c-12.8 8.533-17.067 21.333-17.067 34.133 0 17.067 8.533 29.867 21.333 38.4 0 38.4 12.8 76.8 34.133 110.933l-106.667 29.867c-8.533 4.267-8.533 8.533-8.533 17.067 0 4.267 4.267 4.267 4.267 4.267l375.467 119.467h4.267l375.467-123.733c4.267 0 8.533-4.267 8.533-8.533s-4.267-8.533-8.533-12.8zM725.333 234.669c4.267-46.933-93.867-85.333-213.333-85.333s-213.333 38.4-213.333 85.333l4.267 106.667 192-64c4.267 0 12.8 0 17.067 0s12.8 0 17.067 4.267l192 59.733 4.267-106.667z" />
|
||||
<glyph unicode="" glyph-name="info-important-description" data-tags="info-important-description" d="M512 697.368c-188.5 0-341.3-152.8-341.3-341.3s152.8-341.4 341.3-341.4 341.3 152.8 341.3 341.3-152.8 341.4-341.3 341.4v0zM512 43.268c-172.7 0-312.7 140-312.7 312.7s140 312.7 312.7 312.7c172.7 0 312.7-140 312.7-312.7-0.2-172.6-140.1-312.5-312.7-312.7v0zM512 605.568c-137.9 0-249.6-111.8-249.6-249.6s111.7-249.6 249.6-249.6 249.6 111.8 249.6 249.6-111.8 249.6-249.6 249.6v0z" />
|
||||
<glyph unicode="" glyph-name="icon-info" data-tags="icon-info" d="M467.2 459.522h87.467c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v70.385c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-87.462c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-69.959c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.715-4.373 9.2-7.159 15.338-7.245zM597.333 156.589h-22.187v209.92c0.001 0.082 0.002 0.179 0.002 0.276 0 6.188-2.44 11.806-6.409 15.946-3.759 4.19-9.201 6.819-15.257 6.819-0.033 0-0.067 0-0.1 0h-130.128c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h22.182v-139.947h-22.187c-0.028 0-0.062 0-0.095 0-6.056 0-11.499-2.629-15.248-6.808-3.979-4.15-6.419-9.769-6.419-15.957 0-0.097 0.001-0.194 0.002-0.29v-46.492c-0.001-0.082-0.002-0.179-0.002-0.276 0-6.188 2.44-11.806 6.409-15.946 3.759-4.19 9.201-6.819 15.257-6.819 0.033 0 0.067 0 0.1 0h174.075c0.028 0 0.062 0 0.095 0 6.056 0 11.499 2.629 15.248 6.808 3.979 4.15 6.419 9.769 6.419 15.957 0 0.097-0.001 0.194-0.002 0.29v46.065c0.043 0.527 0.067 1.141 0.067 1.761 0 5.302-1.791 10.185-4.8 14.079-3.742 4.424-9.36 7.247-15.636 7.247-0.489 0-0.975-0.017-1.456-0.051z" />
|
||||
<glyph unicode="" glyph-name="paste" data-tags="paste" d="M394.402 702.4h-75.333c-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-56.4 37.6-100.4 87.867-116v69.067c-15.733 9.467-25.067 25.067-25.067 47.067v288.4c0 31.333 25.067 56.4 56.4 56.4h131.733c0 0 0 0 3.2 3.2v0c0 31.333-28.267 59.6-59.6 59.6zM704.802 592.533c0 0-28.267 0-40.8 0-12.533 34.533-43.867 59.6-84.667 59.6s-69.067-25.067-81.6-59.6c-12.533 0-40.8 0-40.8 0-65.867 0-119.2-53.333-119.2-119.2v-288.533c0-65.867 53.333-119.2 119.2-119.2h247.867c65.867 0 119.2 53.333 119.2 119.2v285.467c3.2 65.867-53.2 122.267-119.2 122.267zM582.535 605.2c22 0 40.8-18.8 40.8-40.8s-18.8-40.8-40.8-40.8c-22 0-40.8 18.8-40.8 40.8s15.733 40.8 40.8 40.8zM764.402 181.733c0-31.333-25.067-56.4-56.4-56.4h-250.933c-31.333 0-56.4 25.067-56.4 56.4v288.533c0 18.8 9.467 37.6 25.067 47.067v0c0-43.867 34.533-78.4 78.4-78.4h160c43.867 0 78.4 34.533 78.4 78.4v0c12.533-9.467 22-28.267 22-47.067v-288.533z" />
|
||||
<glyph unicode="" glyph-name="reuse" data-tags="reuse" d="M734.974 624.751c-54.605 61.619-134.123 100.573-222.977 100.573-164.936 0-298.661-133.724-298.661-298.661h74.667c0 123.721 100.272 223.993 223.993 223.993 68.214 0 128.747-30.96 169.766-79.119l-70.213-70.213h199.109v199.109l-75.689-75.689zM511.999 202.671c-68.214 0-128.747 30.96-169.766 79.119l70.213 70.213h-199.109v-199.109l75.689 75.689c54.605-61.619 134.123-100.573 222.977-100.573 164.936 0 298.661 133.724 298.661 298.661h-74.667c0-123.721-100.272-223.993-223.993-223.993z" />
|
||||
<glyph unicode="" glyph-name="info-outlined" data-tags="info-outlined" d="M467.199 181.336h89.599v268.8h-89.599v-268.8zM512 853.335c-247.296 0-448.001-200.705-448.001-448.001s200.705-448.001 448.001-448.001 448.001 200.705 448.001 448.001-200.705 448.001-448.001 448.001zM512 46.936c-197.568 0-358.398 160.83-358.398 358.398s160.83 358.398 358.398 358.398 358.398-160.83 358.398-358.398-160.83-358.398-358.398-358.398zM467.199 539.734h89.599v89.599h-89.599v-89.599z" />
|
||||
<glyph unicode="" glyph-name="spinner" data-tags="spinner" d="M600 808.001c0-48.602-39.398-88-88-88s-88 39.398-88 88 39.398 88 88 88 88-39.398 88-88zM512 133.333c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.398 88-88 88zM893.334 514.667c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.398 88-88 88zM218.666 426.667c0 48.602-39.398 88-88 88s-88-39.398-88-88 39.398-88 88-88 88 39.398 88 88zM242.357 245.024c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88c0 48.6-39.4 88-88 88zM781.643 245.024c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88c0 48.6-39.398 88-88 88zM242.357 784.31c-48.602 0-88-39.398-88-88s39.398-88 88-88 88 39.398 88 88-39.4 88-88 88z" />
|
||||
<glyph unicode="" glyph-name="copy-enabled" data-tags="copy-enabled" d="M614.525 809.292h-317.672c-74.968 0-135.9-60.931-135.9-135.9v-370.388c0-43.817 20.88-82.842 53.231-107.83v70.346c-4.45 11.297-7.016 23.962-7.016 37.484v370.388c0 49.292 40.224 89.516 89.516 89.516h318.014c15.232 0 29.611-3.766 42.107-10.613h68.294c-24.646 34.402-65.039 56.997-110.57 56.997zM674.431 267.403h-209.327c-14.721 0-23.105-8.388-23.105-23.105s8.388-23.105 23.105-23.105h209.327c11.124 0 23.105 8.899 23.105 23.105 0 14.721-8.388 23.105-23.105 23.105zM674.431 388.244h-209.327c-14.721 0-23.105-8.388-23.105-23.105s8.388-23.105 23.105-23.105h209.327c11.124 0 23.105 8.899 23.105 23.105 0 14.721-8.388 23.105-23.105 23.105zM726.978 686.23h-318.014c-74.968 0-135.9-60.931-135.9-135.9v-370.388c0-74.968 60.931-135.9 135.9-135.9v0h318.183c74.968 0 135.9 60.931 135.9 135.9v370.388c-0.342 74.968-61.273 135.9-136.073 135.9zM816.494 183.878c0-49.292-40.224-89.516-89.516-89.516h-318.183c-49.292 0-89.516 40.224-89.516 89.516v366.449c0 49.292 40.224 89.516 89.516 89.516h318.014c49.292 0 89.516-40.224 89.516-89.516v-349.334h0.173v-17.115zM674.431 509.081h-209.327c-14.721 0-23.105-8.388-23.105-23.105s8.388-23.105 23.105-23.105h209.327c11.124 0 23.105 8.899 23.105 23.105 0 14.721-8.388 23.105-23.105 23.105z" />
|
||||
<glyph unicode="" glyph-name="copy-disabled" data-tags="copy-disabled" d="M614.525 809.292h-317.672c-74.968 0-135.9-60.931-135.9-135.9v-370.388c0-43.817 20.88-82.842 53.231-107.83v70.346c-4.45 11.297-7.016 23.962-7.016 37.484v370.388c0 49.292 40.224 89.516 89.516 89.516h318.014c15.232 0 29.611-3.766 42.107-10.613h68.294c-24.646 34.402-65.039 56.997-110.57 56.997zM726.978 686.23h-318.014c-74.968 0-135.9-60.931-135.9-135.9v-370.388c0-74.968 60.931-135.9 135.9-135.9v0h318.183c74.968 0 135.9 60.931 135.9 135.9v370.388c-0.342 74.968-61.273 135.9-136.073 135.9zM816.494 183.878c0-49.292-40.224-89.516-89.516-89.516h-318.183c-49.292 0-89.516 40.224-89.516 89.516v366.449c0 49.292 40.224 89.516 89.516 89.516h318.014c49.292 0 89.516-40.224 89.516-89.516v-349.334h0.173v-17.115zM709.521 468.857l-27.728 27.555-109.544-109.544-109.544 109.544-27.555-27.555 109.544-109.544-109.544-109.544 27.555-27.555 109.544 109.544 109.544-109.544 27.555 27.555-109.544 109.544 109.713 109.544z" />
|
||||
<glyph unicode="" glyph-name="paste-enabled" data-tags="paste-enabled" d="M410.237 57.275c-75.402 0-136.793 61.394-136.793 136.793v373.025c0 75.402 61.394 136.793 136.793 136.793h64.85l4.152 11.413c15.219 41.678 47.732 65.715 89.237 65.715 42.716 0 78.512-25.075 93.212-65.715l4.152-11.413h64.85c37.007 0 73.499-15.911 99.786-43.581 25.594-26.805 38.737-61.048 37.007-96.326v-369.738c0-75.402-61.394-136.793-136.793-136.793l-320.453-0.173zM360.947 638.689c-24.729-15.046-40.64-44.619-40.64-75.575v-373.025c0-49.804 40.467-90.271 90.271-90.271h324.432c43.754 0 80.415 31.476 88.545 72.981h1.73l0.173 17.295v373.025c0 28.708-14.181 58.627-35.277 74.71l-27.67 20.923v-17.468c0-47.213-36.834-84.048-84.048-84.048h-207.351c-47.213 0-84.048 36.834-84.048 84.048v13.489l-26.113-16.084zM572.451 733.631c-27.843 0-48.766-20.923-48.766-48.766 0-26.459 22.307-48.766 48.766-48.766s48.766 22.307 48.766 48.766c0.173 26.286-22.134 48.766-48.766 48.766zM422.169 764.069c7.78 8.818 16.603 16.776 24.383 25.594 1.903 2.076 3.806 4.325 5.709 6.401h-158.585c-75.748 0-137.312-61.567-137.312-137.312v-374.236c0-44.965 21.788-84.913 55.167-109.988v68.829c-5.536 12.278-8.472 26.113-8.472 41.159v374.236c0 49.804 40.64 90.444 90.444 90.444h116.561c3.633 5.19 7.78 10.202 12.105 14.873z" />
|
||||
<glyph unicode="" glyph-name="paste-disabled" data-tags="paste-disabled" d="M410.237 57.275c-75.402 0-136.793 61.394-136.793 136.793v373.025c0 75.402 61.394 136.793 136.793 136.793h64.85l4.152 11.413c15.219 41.678 47.732 65.715 89.237 65.715 42.716 0 78.512-25.075 93.212-65.715l4.152-11.413h64.85c37.007 0 73.499-15.911 99.786-43.581 25.594-26.805 38.737-61.048 37.007-96.326v-369.738c0-75.402-61.394-136.793-136.793-136.793l-320.453-0.173zM360.947 638.689c-24.729-15.046-40.64-44.619-40.64-75.575v-373.025c0-49.804 40.467-90.271 90.271-90.271h324.432c43.754 0 80.415 31.476 88.545 72.981h1.73l0.173 17.295v373.025c0 28.708-14.181 58.627-35.277 74.71l-27.67 20.923v-17.468c0-47.213-36.834-84.048-84.048-84.048h-207.351c-47.213 0-84.048 36.834-84.048 84.048v13.489l-26.113-16.084zM572.451 733.631c-27.843 0-48.766-20.923-48.766-48.766 0-26.459 22.307-48.766 48.766-48.766s48.766 22.307 48.766 48.766c0.173 26.286-22.134 48.766-48.766 48.766zM422.169 764.069c7.78 8.818 16.603 16.776 24.383 25.594 1.903 2.076 3.806 4.325 5.709 6.401h-158.585c-75.748 0-137.312-61.567-137.312-137.312v-374.236c0-44.965 21.788-84.913 55.167-109.988v68.829c-5.536 12.278-8.472 26.113-8.472 41.159v374.236c0 49.804 40.64 90.444 90.444 90.444h116.561c3.633 5.19 7.78 10.202 12.105 14.873zM718.585 463.848l-28.016 27.843-110.68-110.68-110.68 110.68-27.843-27.843 110.68-110.68-110.68-110.68 27.843-27.843 110.68 110.68 110.68-110.68 27.843 27.843-110.68 110.68 110.853 110.68z" />
|
||||
<glyph unicode="" glyph-name="button-disabled" data-tags="button-disabled" d="M853.333 699.246l-68.754 68.754-272.579-272.579-272.579 272.579-68.754-68.754 272.579-272.579-272.579-272.579 68.754-68.754 272.579 272.579 272.579-272.579 68.754 68.754-272.579 272.579z" />
|
||||
</font></defs></svg>
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* @class
|
||||
* @augments H5P.EventDispatcher
|
||||
* @param {Object} displayOptions
|
||||
* @param {boolean} displayOptions.export Triggers the display of the 'Download' button
|
||||
* @param {boolean} displayOptions.copyright Triggers the display of the 'Copyright' button
|
||||
* @param {boolean} displayOptions.embed Triggers the display of the 'Embed' button
|
||||
* @param {boolean} displayOptions.icon Triggers the display of the 'H5P icon' link
|
||||
*/
|
||||
H5P.ActionBar = (function ($, EventDispatcher) {
|
||||
"use strict";
|
||||
|
||||
function ActionBar(displayOptions) {
|
||||
EventDispatcher.call(this);
|
||||
|
||||
/** @alias H5P.ActionBar# */
|
||||
var self = this;
|
||||
|
||||
var hasActions = false;
|
||||
|
||||
// Create action bar
|
||||
var $actions = H5P.jQuery('<ul class="h5p-actions"></ul>');
|
||||
|
||||
/**
|
||||
* Helper for creating action bar buttons.
|
||||
*
|
||||
* @private
|
||||
* @param {string} type
|
||||
* @param {string} customClass Instead of type class
|
||||
*/
|
||||
var addActionButton = function (type, customClass) {
|
||||
/**
|
||||
* Handles selection of action
|
||||
*/
|
||||
var handler = function () {
|
||||
self.trigger(type);
|
||||
};
|
||||
H5P.jQuery('<li/>', {
|
||||
'class': 'h5p-button h5p-noselect h5p-' + (customClass ? customClass : type),
|
||||
role: 'button',
|
||||
tabindex: 0,
|
||||
title: H5P.t(type + 'Description'),
|
||||
html: H5P.t(type),
|
||||
on: {
|
||||
click: handler,
|
||||
keypress: function (e) {
|
||||
if (e.which === 32) {
|
||||
handler();
|
||||
e.preventDefault(); // (since return false will block other inputs)
|
||||
}
|
||||
}
|
||||
},
|
||||
appendTo: $actions
|
||||
});
|
||||
|
||||
hasActions = true;
|
||||
};
|
||||
|
||||
// Register action bar buttons
|
||||
if (displayOptions.export || displayOptions.copy) {
|
||||
// Add export button
|
||||
addActionButton('reuse', 'export');
|
||||
}
|
||||
if (displayOptions.copyright) {
|
||||
addActionButton('copyrights');
|
||||
}
|
||||
if (displayOptions.embed) {
|
||||
addActionButton('embed');
|
||||
}
|
||||
if (displayOptions.icon) {
|
||||
// Add about H5P button icon
|
||||
H5P.jQuery('<li><a class="h5p-link" href="http://h5p.org" target="_blank" title="' + H5P.t('h5pDescription') + '"></a></li>').appendTo($actions);
|
||||
hasActions = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the dom element
|
||||
*
|
||||
* @return {H5P.jQuery}
|
||||
*/
|
||||
self.getDOMElement = function () {
|
||||
return $actions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Does the actionbar contain actions?
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
self.hasActions = function () {
|
||||
return hasActions;
|
||||
};
|
||||
}
|
||||
|
||||
ActionBar.prototype = Object.create(EventDispatcher.prototype);
|
||||
ActionBar.prototype.constructor = ActionBar;
|
||||
|
||||
return ActionBar;
|
||||
|
||||
})(H5P.jQuery, H5P.EventDispatcher);
|
|
@ -0,0 +1,410 @@
|
|||
/*global H5P*/
|
||||
H5P.ConfirmationDialog = (function (EventDispatcher) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Create a confirmation dialog
|
||||
*
|
||||
* @param [options] Options for confirmation dialog
|
||||
* @param [options.instance] Instance that uses confirmation dialog
|
||||
* @param [options.headerText] Header text
|
||||
* @param [options.dialogText] Dialog text
|
||||
* @param [options.cancelText] Cancel dialog button text
|
||||
* @param [options.confirmText] Confirm dialog button text
|
||||
* @param [options.hideCancel] Hide cancel button
|
||||
* @param [options.hideExit] Hide exit button
|
||||
* @param [options.skipRestoreFocus] Skip restoring focus when hiding the dialog
|
||||
* @param [options.classes] Extra classes for popup
|
||||
* @constructor
|
||||
*/
|
||||
function ConfirmationDialog(options) {
|
||||
EventDispatcher.call(this);
|
||||
var self = this;
|
||||
|
||||
// Make sure confirmation dialogs have unique id
|
||||
H5P.ConfirmationDialog.uniqueId += 1;
|
||||
var uniqueId = H5P.ConfirmationDialog.uniqueId;
|
||||
|
||||
// Default options
|
||||
options = options || {};
|
||||
options.headerText = options.headerText || H5P.t('confirmDialogHeader');
|
||||
options.dialogText = options.dialogText || H5P.t('confirmDialogBody');
|
||||
options.cancelText = options.cancelText || H5P.t('cancelLabel');
|
||||
options.confirmText = options.confirmText || H5P.t('confirmLabel');
|
||||
|
||||
/**
|
||||
* Handle confirming event
|
||||
* @param {Event} e
|
||||
*/
|
||||
function dialogConfirmed(e) {
|
||||
self.hide();
|
||||
self.trigger('confirmed');
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dialog canceled
|
||||
* @param {Event} e
|
||||
*/
|
||||
function dialogCanceled(e) {
|
||||
self.hide();
|
||||
self.trigger('canceled');
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow focus to element
|
||||
* @param {HTMLElement} element Next element to be focused
|
||||
* @param {Event} e Original tab event
|
||||
*/
|
||||
function flowTo(element, e) {
|
||||
element.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Offset of exit button
|
||||
var exitButtonOffset = 2 * 16;
|
||||
var shadowOffset = 8;
|
||||
|
||||
// Determine if we are too large for our container and must resize
|
||||
var resizeIFrame = false;
|
||||
|
||||
// Create background
|
||||
var popupBackground = document.createElement('div');
|
||||
popupBackground.classList
|
||||
.add('h5p-confirmation-dialog-background', 'hidden', 'hiding');
|
||||
|
||||
// Create outer popup
|
||||
var popup = document.createElement('div');
|
||||
popup.classList.add('h5p-confirmation-dialog-popup', 'hidden');
|
||||
if (options.classes) {
|
||||
options.classes.forEach(function (popupClass) {
|
||||
popup.classList.add(popupClass);
|
||||
});
|
||||
}
|
||||
|
||||
popup.setAttribute('role', 'dialog');
|
||||
popup.setAttribute('aria-labelledby', 'h5p-confirmation-dialog-dialog-text-' + uniqueId);
|
||||
popupBackground.appendChild(popup);
|
||||
popup.addEventListener('keydown', function (e) {
|
||||
if (e.which === 27) {// Esc key
|
||||
// Exit dialog
|
||||
dialogCanceled(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Popup header
|
||||
var header = document.createElement('div');
|
||||
header.classList.add('h5p-confirmation-dialog-header');
|
||||
popup.appendChild(header);
|
||||
|
||||
// Header text
|
||||
var headerText = document.createElement('div');
|
||||
headerText.classList.add('h5p-confirmation-dialog-header-text');
|
||||
headerText.innerHTML = options.headerText;
|
||||
header.appendChild(headerText);
|
||||
|
||||
// Popup body
|
||||
var body = document.createElement('div');
|
||||
body.classList.add('h5p-confirmation-dialog-body');
|
||||
popup.appendChild(body);
|
||||
|
||||
// Popup text
|
||||
var text = document.createElement('div');
|
||||
text.classList.add('h5p-confirmation-dialog-text');
|
||||
text.innerHTML = options.dialogText;
|
||||
text.id = 'h5p-confirmation-dialog-dialog-text-' + uniqueId;
|
||||
body.appendChild(text);
|
||||
|
||||
// Popup buttons
|
||||
var buttons = document.createElement('div');
|
||||
buttons.classList.add('h5p-confirmation-dialog-buttons');
|
||||
body.appendChild(buttons);
|
||||
|
||||
// Cancel button
|
||||
var cancelButton = document.createElement('button');
|
||||
cancelButton.classList.add('h5p-core-cancel-button');
|
||||
cancelButton.textContent = options.cancelText;
|
||||
|
||||
// Confirm button
|
||||
var confirmButton = document.createElement('button');
|
||||
confirmButton.classList.add('h5p-core-button');
|
||||
confirmButton.classList.add('h5p-confirmation-dialog-confirm-button');
|
||||
confirmButton.textContent = options.confirmText;
|
||||
|
||||
// Exit button
|
||||
var exitButton = document.createElement('button');
|
||||
exitButton.classList.add('h5p-confirmation-dialog-exit');
|
||||
exitButton.setAttribute('aria-hidden', 'true');
|
||||
exitButton.tabIndex = -1;
|
||||
exitButton.title = options.cancelText;
|
||||
|
||||
// Cancel handler
|
||||
cancelButton.addEventListener('click', dialogCanceled);
|
||||
cancelButton.addEventListener('keydown', function (e) {
|
||||
if (e.which === 32) { // Space
|
||||
dialogCanceled(e);
|
||||
}
|
||||
else if (e.which === 9 && e.shiftKey) { // Shift-tab
|
||||
flowTo(confirmButton, e);
|
||||
}
|
||||
});
|
||||
|
||||
if (!options.hideCancel) {
|
||||
buttons.appendChild(cancelButton);
|
||||
}
|
||||
else {
|
||||
// Center buttons
|
||||
buttons.classList.add('center');
|
||||
}
|
||||
|
||||
// Confirm handler
|
||||
confirmButton.addEventListener('click', dialogConfirmed);
|
||||
confirmButton.addEventListener('keydown', function (e) {
|
||||
if (e.which === 32) { // Space
|
||||
dialogConfirmed(e);
|
||||
}
|
||||
else if (e.which === 9 && !e.shiftKey) { // Tab
|
||||
const nextButton = !options.hideCancel ? cancelButton : confirmButton;
|
||||
flowTo(nextButton, e);
|
||||
}
|
||||
});
|
||||
buttons.appendChild(confirmButton);
|
||||
|
||||
// Exit handler
|
||||
exitButton.addEventListener('click', dialogCanceled);
|
||||
exitButton.addEventListener('keydown', function (e) {
|
||||
if (e.which === 32) { // Space
|
||||
dialogCanceled(e);
|
||||
}
|
||||
});
|
||||
if (!options.hideExit) {
|
||||
popup.appendChild(exitButton);
|
||||
}
|
||||
|
||||
// Wrapper element
|
||||
var wrapperElement;
|
||||
|
||||
// Focus capturing
|
||||
var focusPredator;
|
||||
|
||||
// Maintains hidden state of elements
|
||||
var wrapperSiblingsHidden = [];
|
||||
var popupSiblingsHidden = [];
|
||||
|
||||
// Element with focus before dialog
|
||||
var previouslyFocused;
|
||||
|
||||
/**
|
||||
* Set parent of confirmation dialog
|
||||
* @param {HTMLElement} wrapper
|
||||
* @returns {H5P.ConfirmationDialog}
|
||||
*/
|
||||
this.appendTo = function (wrapper) {
|
||||
wrapperElement = wrapper;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Capture the focus element, send it to confirmation button
|
||||
* @param {Event} e Original focus event
|
||||
*/
|
||||
var captureFocus = function (e) {
|
||||
if (!popupBackground.contains(e.target)) {
|
||||
e.preventDefault();
|
||||
confirmButton.focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide siblings of element from assistive technology
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {Array} The previous hidden state of all siblings
|
||||
*/
|
||||
var hideSiblings = function (element) {
|
||||
var hiddenSiblings = [];
|
||||
var siblings = element.parentNode.children;
|
||||
var i;
|
||||
for (i = 0; i < siblings.length; i += 1) {
|
||||
// Preserve hidden state
|
||||
hiddenSiblings[i] = siblings[i].getAttribute('aria-hidden') ?
|
||||
true : false;
|
||||
|
||||
if (siblings[i] !== element) {
|
||||
siblings[i].setAttribute('aria-hidden', true);
|
||||
}
|
||||
}
|
||||
return hiddenSiblings;
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores assistive technology state of element's siblings
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {Array} hiddenSiblings Hidden state of all siblings
|
||||
*/
|
||||
var restoreSiblings = function (element, hiddenSiblings) {
|
||||
var siblings = element.parentNode.children;
|
||||
var i;
|
||||
for (i = 0; i < siblings.length; i += 1) {
|
||||
if (siblings[i] !== element && !hiddenSiblings[i]) {
|
||||
siblings[i].removeAttribute('aria-hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start capturing focus of parent and send it to dialog
|
||||
*/
|
||||
var startCapturingFocus = function () {
|
||||
focusPredator = wrapperElement.parentNode || wrapperElement;
|
||||
focusPredator.addEventListener('focus', captureFocus, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean up event listener for capturing focus
|
||||
*/
|
||||
var stopCapturingFocus = function () {
|
||||
focusPredator.removeAttribute('aria-hidden');
|
||||
focusPredator.removeEventListener('focus', captureFocus, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide siblings in underlay from assistive technologies
|
||||
*/
|
||||
var disableUnderlay = function () {
|
||||
wrapperSiblingsHidden = hideSiblings(wrapperElement);
|
||||
popupSiblingsHidden = hideSiblings(popupBackground);
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore state of underlay for assistive technologies
|
||||
*/
|
||||
var restoreUnderlay = function () {
|
||||
restoreSiblings(wrapperElement, wrapperSiblingsHidden);
|
||||
restoreSiblings(popupBackground, popupSiblingsHidden);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fit popup to container. Makes sure it doesn't overflow.
|
||||
* @params {number} [offsetTop] Offset of popup
|
||||
*/
|
||||
var fitToContainer = function (offsetTop) {
|
||||
var popupOffsetTop = parseInt(popup.style.top, 10);
|
||||
if (offsetTop !== undefined) {
|
||||
popupOffsetTop = offsetTop;
|
||||
}
|
||||
|
||||
if (!popupOffsetTop) {
|
||||
popupOffsetTop = 0;
|
||||
}
|
||||
|
||||
// Overflows height
|
||||
if (popupOffsetTop + popup.offsetHeight > wrapperElement.offsetHeight) {
|
||||
popupOffsetTop = wrapperElement.offsetHeight - popup.offsetHeight - shadowOffset;
|
||||
}
|
||||
|
||||
if (popupOffsetTop - exitButtonOffset <= 0) {
|
||||
popupOffsetTop = exitButtonOffset + shadowOffset;
|
||||
|
||||
// We are too big and must resize
|
||||
resizeIFrame = true;
|
||||
}
|
||||
popup.style.top = popupOffsetTop + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
* Show confirmation dialog
|
||||
* @params {number} offsetTop Offset top
|
||||
* @returns {H5P.ConfirmationDialog}
|
||||
*/
|
||||
this.show = function (offsetTop) {
|
||||
// Capture focused item
|
||||
previouslyFocused = document.activeElement;
|
||||
wrapperElement.appendChild(popupBackground);
|
||||
startCapturingFocus();
|
||||
disableUnderlay();
|
||||
popupBackground.classList.remove('hidden');
|
||||
fitToContainer(offsetTop);
|
||||
setTimeout(function () {
|
||||
popup.classList.remove('hidden');
|
||||
popupBackground.classList.remove('hiding');
|
||||
|
||||
setTimeout(function () {
|
||||
// Focus confirm button
|
||||
confirmButton.focus();
|
||||
|
||||
// Resize iFrame if necessary
|
||||
if (resizeIFrame && options.instance) {
|
||||
var minHeight = parseInt(popup.offsetHeight, 10) +
|
||||
exitButtonOffset + (2 * shadowOffset);
|
||||
self.setViewPortMinimumHeight(minHeight);
|
||||
options.instance.trigger('resize');
|
||||
resizeIFrame = false;
|
||||
}
|
||||
}, 100);
|
||||
}, 0);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide confirmation dialog
|
||||
* @returns {H5P.ConfirmationDialog}
|
||||
*/
|
||||
this.hide = function () {
|
||||
popupBackground.classList.add('hiding');
|
||||
popup.classList.add('hidden');
|
||||
|
||||
// Restore focus
|
||||
stopCapturingFocus();
|
||||
if (!options.skipRestoreFocus) {
|
||||
previouslyFocused.focus();
|
||||
}
|
||||
restoreUnderlay();
|
||||
setTimeout(function () {
|
||||
popupBackground.classList.add('hidden');
|
||||
wrapperElement.removeChild(popupBackground);
|
||||
self.setViewPortMinimumHeight(null);
|
||||
}, 100);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve element
|
||||
*
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
this.getElement = function () {
|
||||
return popup;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get previously focused element
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
this.getPreviouslyFocused = function () {
|
||||
return previouslyFocused;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the minimum height of the view port
|
||||
*
|
||||
* @param {number|null} minHeight
|
||||
*/
|
||||
this.setViewPortMinimumHeight = function (minHeight) {
|
||||
var container = document.querySelector('.h5p-container') || document.body;
|
||||
container.style.minHeight = (typeof minHeight === 'number') ? (minHeight + 'px') : minHeight;
|
||||
};
|
||||
}
|
||||
|
||||
ConfirmationDialog.prototype = Object.create(EventDispatcher.prototype);
|
||||
ConfirmationDialog.prototype.constructor = ConfirmationDialog;
|
||||
|
||||
return ConfirmationDialog;
|
||||
|
||||
}(H5P.EventDispatcher));
|
||||
|
||||
H5P.ConfirmationDialog.uniqueId = -1;
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* H5P.ContentType is a base class for all content types. Used by newRunnable()
|
||||
*
|
||||
* Functions here may be overridable by the libraries. In special cases,
|
||||
* it is also possible to override H5P.ContentType on a global level.
|
||||
*
|
||||
* NOTE that this doesn't actually 'extend' the event dispatcher but instead
|
||||
* it creates a single instance which all content types shares as their base
|
||||
* prototype. (in some cases this may be the root of strange event behavior)
|
||||
*
|
||||
* @class
|
||||
* @augments H5P.EventDispatcher
|
||||
*/
|
||||
H5P.ContentType = function (isRootLibrary) {
|
||||
|
||||
function ContentType() {}
|
||||
|
||||
// Inherit from EventDispatcher.
|
||||
ContentType.prototype = new H5P.EventDispatcher();
|
||||
|
||||
/**
|
||||
* Is library standalone or not? Not beeing standalone, means it is
|
||||
* included in another library
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
ContentType.prototype.isRoot = function () {
|
||||
return isRootLibrary;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the file path of a file in the current library
|
||||
* @param {string} filePath The path to the file relative to the library folder
|
||||
* @return {string} The full path to the file
|
||||
*/
|
||||
ContentType.prototype.getLibraryFilePath = function (filePath) {
|
||||
return H5P.getLibraryPath(this.libraryInfo.versionedNameNoSpaces) + '/' + filePath;
|
||||
};
|
||||
|
||||
return ContentType;
|
||||
};
|
|
@ -0,0 +1,313 @@
|
|||
/*jshint -W083 */
|
||||
var H5PUpgrades = H5PUpgrades || {};
|
||||
|
||||
H5P.ContentUpgradeProcess = (function (Version) {
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @namespace H5P
|
||||
*/
|
||||
function ContentUpgradeProcess(name, oldVersion, newVersion, params, id, loadLibrary, done) {
|
||||
var self = this;
|
||||
|
||||
// Make params possible to work with
|
||||
try {
|
||||
params = JSON.parse(params);
|
||||
if (!(params instanceof Object)) {
|
||||
throw true;
|
||||
}
|
||||
}
|
||||
catch (event) {
|
||||
return done({
|
||||
type: 'errorParamsBroken',
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
self.loadLibrary = loadLibrary;
|
||||
self.upgrade(name, oldVersion, newVersion, params.params, params.metadata, function (err, upgradedParams, upgradedMetadata) {
|
||||
if (err) {
|
||||
err.id = id;
|
||||
return done(err);
|
||||
}
|
||||
|
||||
done(null, JSON.stringify({params: upgradedParams, metadata: upgradedMetadata}));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run content upgrade.
|
||||
*
|
||||
* @public
|
||||
* @param {string} name
|
||||
* @param {Version} oldVersion
|
||||
* @param {Version} newVersion
|
||||
* @param {Object} params
|
||||
* @param {Object} metadata
|
||||
* @param {Function} done
|
||||
*/
|
||||
ContentUpgradeProcess.prototype.upgrade = function (name, oldVersion, newVersion, params, metadata, done) {
|
||||
var self = this;
|
||||
|
||||
// Load library details and upgrade routines
|
||||
self.loadLibrary(name, newVersion, function (err, library) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
if (library.semantics === null) {
|
||||
return done({
|
||||
type: 'libraryMissing',
|
||||
library: library.name + ' ' + library.version.major + '.' + library.version.minor
|
||||
});
|
||||
}
|
||||
|
||||
// Run upgrade routines on params
|
||||
self.processParams(library, oldVersion, newVersion, params, metadata, function (err, params, metadata) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
// Check if any of the sub-libraries need upgrading
|
||||
asyncSerial(library.semantics, function (index, field, next) {
|
||||
self.processField(field, params[field.name], function (err, upgradedParams) {
|
||||
if (upgradedParams) {
|
||||
params[field.name] = upgradedParams;
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
}, function (err) {
|
||||
done(err, params, metadata);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Run upgrade hooks on params.
|
||||
*
|
||||
* @public
|
||||
* @param {Object} library
|
||||
* @param {Version} oldVersion
|
||||
* @param {Version} newVersion
|
||||
* @param {Object} params
|
||||
* @param {Function} next
|
||||
*/
|
||||
ContentUpgradeProcess.prototype.processParams = function (library, oldVersion, newVersion, params, metadata, next) {
|
||||
if (H5PUpgrades[library.name] === undefined) {
|
||||
if (library.upgradesScript) {
|
||||
// Upgrades script should be loaded so the upgrades should be here.
|
||||
return next({
|
||||
type: 'scriptMissing',
|
||||
library: library.name + ' ' + newVersion
|
||||
});
|
||||
}
|
||||
|
||||
// No upgrades script. Move on
|
||||
return next(null, params, metadata);
|
||||
}
|
||||
|
||||
// Run upgrade hooks. Start by going through major versions
|
||||
asyncSerial(H5PUpgrades[library.name], function (major, minors, nextMajor) {
|
||||
if (major < oldVersion.major || major > newVersion.major) {
|
||||
// Older than the current version or newer than the selected
|
||||
nextMajor();
|
||||
}
|
||||
else {
|
||||
// Go through the minor versions for this major version
|
||||
asyncSerial(minors, function (minor, upgrade, nextMinor) {
|
||||
minor =+ minor;
|
||||
if (minor <= oldVersion.minor || minor > newVersion.minor) {
|
||||
// Older than or equal to the current version or newer than the selected
|
||||
nextMinor();
|
||||
}
|
||||
else {
|
||||
// We found an upgrade hook, run it
|
||||
var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade);
|
||||
|
||||
try {
|
||||
unnecessaryWrapper(params, function (err, upgradedParams, upgradedExtras) {
|
||||
params = upgradedParams;
|
||||
if (upgradedExtras && upgradedExtras.metadata) { // Optional
|
||||
metadata = upgradedExtras.metadata;
|
||||
}
|
||||
nextMinor(err);
|
||||
}, {metadata: metadata});
|
||||
}
|
||||
catch (err) {
|
||||
if (console && console.error) {
|
||||
console.error("Error", err.stack);
|
||||
console.error("Error", err.name);
|
||||
console.error("Error", err.message);
|
||||
}
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
}, nextMajor);
|
||||
}
|
||||
}, function (err) {
|
||||
next(err, params, metadata);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Process parameter fields to find and upgrade sub-libraries.
|
||||
*
|
||||
* @public
|
||||
* @param {Object} field
|
||||
* @param {Object} params
|
||||
* @param {Function} done
|
||||
*/
|
||||
ContentUpgradeProcess.prototype.processField = function (field, params, done) {
|
||||
var self = this;
|
||||
|
||||
if (params === undefined) {
|
||||
return done();
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case 'library':
|
||||
if (params.library === undefined || params.params === undefined) {
|
||||
return done();
|
||||
}
|
||||
|
||||
// Look for available upgrades
|
||||
var usedLib = params.library.split(' ', 2);
|
||||
for (var i = 0; i < field.options.length; i++) {
|
||||
var availableLib = (typeof field.options[i] === 'string') ? field.options[i].split(' ', 2) : field.options[i].name.split(' ', 2);
|
||||
if (availableLib[0] === usedLib[0]) {
|
||||
if (availableLib[1] === usedLib[1]) {
|
||||
return done(); // Same version
|
||||
}
|
||||
|
||||
// We have different versions
|
||||
var usedVer = new Version(usedLib[1]);
|
||||
var availableVer = new Version(availableLib[1]);
|
||||
if (usedVer.major > availableVer.major || (usedVer.major === availableVer.major && usedVer.minor >= availableVer.minor)) {
|
||||
return done({
|
||||
type: 'errorTooHighVersion',
|
||||
used: usedLib[0] + ' ' + usedVer,
|
||||
supported: availableLib[0] + ' ' + availableVer
|
||||
}); // Larger or same version that's available
|
||||
}
|
||||
|
||||
// A newer version is available, upgrade params
|
||||
return self.upgrade(availableLib[0], usedVer, availableVer, params.params, params.metadata, function (err, upgradedParams, upgradedMetadata) {
|
||||
if (!err) {
|
||||
params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor;
|
||||
params.params = upgradedParams;
|
||||
if (upgradedMetadata) {
|
||||
params.metadata = upgradedMetadata;
|
||||
}
|
||||
}
|
||||
done(err, params);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Content type was not supporte by the higher version
|
||||
done({
|
||||
type: 'errorNotSupported',
|
||||
used: usedLib[0] + ' ' + usedVer
|
||||
});
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
if (field.fields.length === 1 && field.isSubContent !== true) {
|
||||
// Single field to process, wrapper will be skipped
|
||||
self.processField(field.fields[0], params, function (err, upgradedParams) {
|
||||
if (upgradedParams) {
|
||||
params = upgradedParams;
|
||||
}
|
||||
done(err, params);
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Go through all fields in the group
|
||||
asyncSerial(field.fields, function (index, subField, next) {
|
||||
var paramsToProcess = params ? params[subField.name] : null;
|
||||
self.processField(subField, paramsToProcess, function (err, upgradedParams) {
|
||||
if (upgradedParams) {
|
||||
params[subField.name] = upgradedParams;
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
|
||||
}, function (err) {
|
||||
done(err, params);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
// Go trough all params in the list
|
||||
asyncSerial(params, function (index, subParams, next) {
|
||||
self.processField(field.field, subParams, function (err, upgradedParams) {
|
||||
if (upgradedParams) {
|
||||
params[index] = upgradedParams;
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
}, function (err) {
|
||||
done(err, params);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helps process each property on the given object asynchronously in serial order.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} obj
|
||||
* @param {Function} process
|
||||
* @param {Function} finished
|
||||
*/
|
||||
var asyncSerial = function (obj, process, finished) {
|
||||
var id, isArray = obj instanceof Array;
|
||||
|
||||
// Keep track of each property that belongs to this object.
|
||||
if (!isArray) {
|
||||
var ids = [];
|
||||
for (id in obj) {
|
||||
if (obj.hasOwnProperty(id)) {
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var i = -1; // Keeps track of the current property
|
||||
|
||||
/**
|
||||
* Private. Process the next property
|
||||
*/
|
||||
var next = function () {
|
||||
id = isArray ? i : ids[i];
|
||||
process(id, obj[id], check);
|
||||
};
|
||||
|
||||
/**
|
||||
* Private. Check if we're done or have an error.
|
||||
*
|
||||
* @param {String} err
|
||||
*/
|
||||
var check = function (err) {
|
||||
// We need to use a real async function in order for the stack to clear.
|
||||
setTimeout(function () {
|
||||
i++;
|
||||
if (i === (isArray ? obj.length : ids.length) || (err !== undefined && err !== null)) {
|
||||
finished(err);
|
||||
}
|
||||
else {
|
||||
next();
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
check(); // Start
|
||||
};
|
||||
|
||||
return ContentUpgradeProcess;
|
||||
})(H5P.Version);
|
|
@ -0,0 +1,63 @@
|
|||
/* global importScripts */
|
||||
var H5P = H5P || {};
|
||||
importScripts('h5p-version.js', 'h5p-content-upgrade-process.js');
|
||||
|
||||
var libraryLoadedCallback;
|
||||
|
||||
/**
|
||||
* Register message handlers
|
||||
*/
|
||||
var messageHandlers = {
|
||||
newJob: function (job) {
|
||||
// Start new job
|
||||
new H5P.ContentUpgradeProcess(job.name, new H5P.Version(job.oldVersion), new H5P.Version(job.newVersion), job.params, job.id, function loadLibrary(name, version, next) {
|
||||
// TODO: Cache?
|
||||
postMessage({
|
||||
action: 'loadLibrary',
|
||||
name: name,
|
||||
version: version.toString()
|
||||
});
|
||||
libraryLoadedCallback = next;
|
||||
}, function done(err, result) {
|
||||
if (err) {
|
||||
// Return error
|
||||
postMessage({
|
||||
action: 'error',
|
||||
id: job.id,
|
||||
err: err.message ? err.message : err
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Return upgraded content
|
||||
postMessage({
|
||||
action: 'done',
|
||||
id: job.id,
|
||||
params: result
|
||||
});
|
||||
});
|
||||
},
|
||||
libraryLoaded: function (data) {
|
||||
var library = data.library;
|
||||
if (library.upgradesScript) {
|
||||
try {
|
||||
importScripts(library.upgradesScript);
|
||||
}
|
||||
catch (err) {
|
||||
libraryLoadedCallback(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
libraryLoadedCallback(null, data.library);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle messages from our master
|
||||
*/
|
||||
onmessage = function (event) {
|
||||
if (event.data.action !== undefined && messageHandlers[event.data.action]) {
|
||||
messageHandlers[event.data.action].call(this, event.data);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,445 @@
|
|||
/* global H5PAdminIntegration H5PUtils */
|
||||
|
||||
(function ($, Version) {
|
||||
var info, $log, $container, librariesCache = {}, scriptsCache = {};
|
||||
|
||||
// Initialize
|
||||
$(document).ready(function () {
|
||||
// Get library info
|
||||
info = H5PAdminIntegration.libraryInfo;
|
||||
|
||||
// Get and reset container
|
||||
const $wrapper = $('#h5p-admin-container').html('');
|
||||
$log = $('<ul class="content-upgrade-log"></ul>').appendTo($wrapper);
|
||||
$container = $('<div><p>' + info.message + '</p></div>').appendTo($wrapper);
|
||||
|
||||
// Make it possible to select version
|
||||
var $version = $(getVersionSelect(info.versions)).appendTo($container);
|
||||
|
||||
// Add "go" button
|
||||
$('<button/>', {
|
||||
class: 'h5p-admin-upgrade-button',
|
||||
text: info.buttonLabel,
|
||||
click: function () {
|
||||
// Start new content upgrade
|
||||
new ContentUpgrade($version.val());
|
||||
}
|
||||
}).appendTo($container);
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate html for version select.
|
||||
*
|
||||
* @param {Object} versions
|
||||
* @returns {String}
|
||||
*/
|
||||
var getVersionSelect = function (versions) {
|
||||
var html = '';
|
||||
for (var id in versions) {
|
||||
html += '<option value="' + id + '">' + versions[id] + '</option>';
|
||||
}
|
||||
if (html !== '') {
|
||||
html = '<select>' + html + '</select>';
|
||||
return html;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays a throbber in the status field.
|
||||
*
|
||||
* @param {String} msg
|
||||
* @returns {_L1.Throbber}
|
||||
*/
|
||||
function Throbber(msg) {
|
||||
var $throbber = H5PUtils.throbber(msg);
|
||||
$container.html('').append($throbber);
|
||||
|
||||
/**
|
||||
* Makes it possible to set the progress.
|
||||
*
|
||||
* @param {String} progress
|
||||
*/
|
||||
this.setProgress = function (progress) {
|
||||
$throbber.text(msg + ' ' + progress);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new content upgrade.
|
||||
*
|
||||
* @param {Number} libraryId
|
||||
* @returns {_L1.ContentUpgrade}
|
||||
*/
|
||||
function ContentUpgrade(libraryId) {
|
||||
var self = this;
|
||||
|
||||
// Get selected version
|
||||
self.version = new Version(info.versions[libraryId]);
|
||||
self.version.libraryId = libraryId;
|
||||
|
||||
// Create throbber with loading text and progress
|
||||
self.throbber = new Throbber(info.inProgress.replace('%ver', self.version));
|
||||
|
||||
self.started = new Date().getTime();
|
||||
self.io = 0;
|
||||
|
||||
// Track number of working
|
||||
self.working = 0;
|
||||
|
||||
var start = function () {
|
||||
// Get the next batch
|
||||
self.nextBatch({
|
||||
libraryId: libraryId,
|
||||
token: info.token
|
||||
});
|
||||
};
|
||||
|
||||
if (window.Worker !== undefined) {
|
||||
// Prepare our workers
|
||||
self.initWorkers();
|
||||
start();
|
||||
}
|
||||
else {
|
||||
// No workers, do the job ourselves
|
||||
self.loadScript(info.scriptBaseUrl + '/h5p-content-upgrade-process.js' + info.buster, start);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize workers
|
||||
*/
|
||||
ContentUpgrade.prototype.initWorkers = function () {
|
||||
var self = this;
|
||||
|
||||
// Determine number of workers (defaults to 4)
|
||||
var numWorkers = (window.navigator !== undefined && window.navigator.hardwareConcurrency ? window.navigator.hardwareConcurrency : 4);
|
||||
self.workers = new Array(numWorkers);
|
||||
|
||||
// Register message handlers
|
||||
var messageHandlers = {
|
||||
done: function (result) {
|
||||
self.workDone(result.id, result.params, this);
|
||||
},
|
||||
error: function (error) {
|
||||
self.printError(error.err);
|
||||
self.workDone(error.id, null, this);
|
||||
},
|
||||
loadLibrary: function (details) {
|
||||
var worker = this;
|
||||
self.loadLibrary(details.name, new Version(details.version), function (err, library) {
|
||||
if (err) {
|
||||
// Reset worker?
|
||||
return;
|
||||
}
|
||||
|
||||
worker.postMessage({
|
||||
action: 'libraryLoaded',
|
||||
library: library
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < numWorkers; i++) {
|
||||
self.workers[i] = new Worker(info.scriptBaseUrl + '/h5p-content-upgrade-worker.js' + info.buster);
|
||||
self.workers[i].onmessage = function (event) {
|
||||
if (event.data.action !== undefined && messageHandlers[event.data.action]) {
|
||||
messageHandlers[event.data.action].call(this, event.data);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the next batch and start processing it.
|
||||
*
|
||||
* @param {Object} outData
|
||||
*/
|
||||
ContentUpgrade.prototype.nextBatch = function (outData) {
|
||||
var self = this;
|
||||
|
||||
// Track time spent on IO
|
||||
var start = new Date().getTime();
|
||||
$.post(info.infoUrl, outData, function (inData) {
|
||||
self.io += new Date().getTime() - start;
|
||||
if (!(inData instanceof Object)) {
|
||||
// Print errors from backend
|
||||
return self.setStatus(inData);
|
||||
}
|
||||
if (inData.left === 0) {
|
||||
var total = new Date().getTime() - self.started;
|
||||
|
||||
if (window.console && console.log) {
|
||||
console.log('The upgrade process took ' + (total / 1000) + ' seconds. (' + (Math.round((self.io / (total / 100)) * 100) / 100) + ' % IO)' );
|
||||
}
|
||||
|
||||
// Terminate workers
|
||||
self.terminate();
|
||||
|
||||
// Nothing left to process
|
||||
return self.setStatus(info.done);
|
||||
}
|
||||
|
||||
self.left = inData.left;
|
||||
self.token = inData.token;
|
||||
|
||||
// Start processing
|
||||
self.processBatch(inData.params, inData.skipped);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set current status message.
|
||||
*
|
||||
* @param {String} msg
|
||||
*/
|
||||
ContentUpgrade.prototype.setStatus = function (msg) {
|
||||
$container.html(msg);
|
||||
};
|
||||
|
||||
/**
|
||||
* Process the given parameters.
|
||||
*
|
||||
* @param {Object} parameters
|
||||
*/
|
||||
ContentUpgrade.prototype.processBatch = function (parameters, skipped) {
|
||||
var self = this;
|
||||
|
||||
// Track upgraded params
|
||||
self.upgraded = {};
|
||||
self.skipped = skipped;
|
||||
|
||||
// Track current batch
|
||||
self.parameters = parameters;
|
||||
|
||||
// Create id mapping
|
||||
self.ids = [];
|
||||
for (var id in parameters) {
|
||||
if (parameters.hasOwnProperty(id)) {
|
||||
self.ids.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of current content
|
||||
self.current = -1;
|
||||
|
||||
if (self.workers !== undefined) {
|
||||
// Assign each worker content to upgrade
|
||||
for (var i = 0; i < self.workers.length; i++) {
|
||||
self.assignWork(self.workers[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
self.assignWork();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ContentUpgrade.prototype.assignWork = function (worker) {
|
||||
var self = this;
|
||||
|
||||
var id = self.ids[self.current + 1];
|
||||
if (id === undefined) {
|
||||
return false; // Out of work
|
||||
}
|
||||
self.current++;
|
||||
self.working++;
|
||||
|
||||
if (worker) {
|
||||
worker.postMessage({
|
||||
action: 'newJob',
|
||||
id: id,
|
||||
name: info.library.name,
|
||||
oldVersion: info.library.version,
|
||||
newVersion: self.version.toString(),
|
||||
params: self.parameters[id]
|
||||
});
|
||||
}
|
||||
else {
|
||||
new H5P.ContentUpgradeProcess(info.library.name, new Version(info.library.version), self.version, self.parameters[id], id, function loadLibrary(name, version, next) {
|
||||
self.loadLibrary(name, version, function (err, library) {
|
||||
if (library.upgradesScript) {
|
||||
self.loadScript(library.upgradesScript, function (err) {
|
||||
if (err) {
|
||||
err = info.errorScript.replace('%lib', name + ' ' + version);
|
||||
}
|
||||
next(err, library);
|
||||
});
|
||||
}
|
||||
else {
|
||||
next(null, library);
|
||||
}
|
||||
});
|
||||
|
||||
}, function done(err, result) {
|
||||
if (err) {
|
||||
self.printError(err);
|
||||
result = null;
|
||||
}
|
||||
|
||||
self.workDone(id, result);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ContentUpgrade.prototype.workDone = function (id, result, worker) {
|
||||
var self = this;
|
||||
|
||||
self.working--;
|
||||
if (result === null) {
|
||||
self.skipped.push(id);
|
||||
}
|
||||
else {
|
||||
self.upgraded[id] = result;
|
||||
}
|
||||
|
||||
// Update progress message
|
||||
self.throbber.setProgress(Math.round((info.total - self.left + self.current) / (info.total / 100)) + ' %');
|
||||
|
||||
// Assign next job
|
||||
if (self.assignWork(worker) === false && self.working === 0) {
|
||||
// All workers have finsihed.
|
||||
self.nextBatch({
|
||||
libraryId: self.version.libraryId,
|
||||
token: self.token,
|
||||
skipped: JSON.stringify(self.skipped),
|
||||
params: JSON.stringify(self.upgraded)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ContentUpgrade.prototype.terminate = function () {
|
||||
var self = this;
|
||||
|
||||
if (self.workers) {
|
||||
// Stop all workers
|
||||
for (var i = 0; i < self.workers.length; i++) {
|
||||
self.workers[i].terminate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var librariesLoadedCallbacks = {};
|
||||
|
||||
/**
|
||||
* Load library data needed for content upgrade.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Version} version
|
||||
* @param {Function} next
|
||||
*/
|
||||
ContentUpgrade.prototype.loadLibrary = function (name, version, next) {
|
||||
var self = this;
|
||||
|
||||
var key = name + '/' + version.major + '/' + version.minor;
|
||||
|
||||
if (librariesCache[key] === true) {
|
||||
// Library is being loaded, que callback
|
||||
if (librariesLoadedCallbacks[key] === undefined) {
|
||||
librariesLoadedCallbacks[key] = [next];
|
||||
return;
|
||||
}
|
||||
librariesLoadedCallbacks[key].push(next);
|
||||
return;
|
||||
}
|
||||
else if (librariesCache[key] !== undefined) {
|
||||
// Library has been loaded before. Return cache.
|
||||
next(null, librariesCache[key]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Track time spent loading
|
||||
var start = new Date().getTime();
|
||||
librariesCache[key] = true;
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
cache: true,
|
||||
url: info.libraryBaseUrl + '/' + key
|
||||
}).fail(function () {
|
||||
self.io += new Date().getTime() - start;
|
||||
next(info.errorData.replace('%lib', name + ' ' + version));
|
||||
}).done(function (library) {
|
||||
self.io += new Date().getTime() - start;
|
||||
librariesCache[key] = library;
|
||||
next(null, library);
|
||||
|
||||
if (librariesLoadedCallbacks[key] !== undefined) {
|
||||
for (var i = 0; i < librariesLoadedCallbacks[key].length; i++) {
|
||||
librariesLoadedCallbacks[key][i](null, library);
|
||||
}
|
||||
}
|
||||
delete librariesLoadedCallbacks[key];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load script with upgrade hooks.
|
||||
*
|
||||
* @param {String} url
|
||||
* @param {Function} next
|
||||
*/
|
||||
ContentUpgrade.prototype.loadScript = function (url, next) {
|
||||
var self = this;
|
||||
|
||||
if (scriptsCache[url] !== undefined) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Track time spent loading
|
||||
var start = new Date().getTime();
|
||||
$.ajax({
|
||||
dataType: 'script',
|
||||
cache: true,
|
||||
url: url
|
||||
}).fail(function () {
|
||||
self.io += new Date().getTime() - start;
|
||||
next(true);
|
||||
}).done(function () {
|
||||
scriptsCache[url] = true;
|
||||
self.io += new Date().getTime() - start;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ContentUpgrade.prototype.printError = function (error) {
|
||||
var self = this;
|
||||
|
||||
switch (error.type) {
|
||||
case 'errorParamsBroken':
|
||||
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
|
||||
break;
|
||||
|
||||
case 'libraryMissing':
|
||||
error = info.errorLibrary.replace('%lib', error.library);
|
||||
break;
|
||||
|
||||
case 'scriptMissing':
|
||||
error = info.errorScript.replace('%lib', error.library);
|
||||
break;
|
||||
|
||||
case 'errorTooHighVersion':
|
||||
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorTooHighVersion.replace('%used', error.used).replace('%supported', error.supported);
|
||||
break;
|
||||
|
||||
case 'errorNotSupported':
|
||||
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorNotSupported.replace('%used', error.used);
|
||||
break;
|
||||
}
|
||||
|
||||
$('<li>' + info.error + '<br/>' + error + '</li>').appendTo($log);
|
||||
};
|
||||
|
||||
})(H5P.jQuery, H5P.Version);
|
|
@ -0,0 +1,442 @@
|
|||
/* global H5PUtils */
|
||||
var H5PDataView = (function ($) {
|
||||
|
||||
/**
|
||||
* Initialize a new H5P data view.
|
||||
*
|
||||
* @class
|
||||
* @param {Object} container
|
||||
* Element to clear out and append to.
|
||||
* @param {String} source
|
||||
* URL to get data from. Data format: {num: 123, rows:[[1,2,3],[2,4,6]]}
|
||||
* @param {Array} headers
|
||||
* List with column headers. Can be strings or objects with options like
|
||||
* "text" and "sortable". E.g.
|
||||
* [{text: 'Col 1', sortable: true}, 'Col 2', 'Col 3']
|
||||
* @param {Object} l10n
|
||||
* Localization / translations. e.g.
|
||||
* {
|
||||
* loading: 'Loading data.',
|
||||
* ajaxFailed: 'Failed to load data.',
|
||||
* noData: "There's no data available that matches your criteria.",
|
||||
* currentPage: 'Page $current of $total',
|
||||
* nextPage: 'Next page',
|
||||
* previousPage: 'Previous page',
|
||||
* search: 'Search'
|
||||
* }
|
||||
* @param {Object} classes
|
||||
* Custom html classes to use on elements.
|
||||
* e.g. {tableClass: 'fixed'}.
|
||||
* @param {Array} filters
|
||||
* Make it possible to filter/search in the given column.
|
||||
* e.g. [null, true, null, null] will make it possible to do a text
|
||||
* search in column 2.
|
||||
* @param {Function} loaded
|
||||
* Callback for when data has been loaded.
|
||||
* @param {Object} order
|
||||
*/
|
||||
function H5PDataView(container, source, headers, l10n, classes, filters, loaded, order) {
|
||||
var self = this;
|
||||
|
||||
self.$container = $(container).addClass('h5p-data-view').html('');
|
||||
|
||||
self.source = source;
|
||||
self.headers = headers;
|
||||
self.l10n = l10n;
|
||||
self.classes = (classes === undefined ? {} : classes);
|
||||
self.filters = (filters === undefined ? [] : filters);
|
||||
self.loaded = loaded;
|
||||
self.order = order;
|
||||
|
||||
self.limit = 20;
|
||||
self.offset = 0;
|
||||
self.filterOn = [];
|
||||
self.facets = {};
|
||||
|
||||
// Index of column with author name; could be made more general by passing database column names and checking for position
|
||||
self.columnIdAuthor = 2;
|
||||
|
||||
// Future option: Create more general solution for filter presets
|
||||
if (H5PIntegration.user && parseInt(H5PIntegration.user.canToggleViewOthersH5PContents) === 1) {
|
||||
self.updateTable([]);
|
||||
self.filterByFacet(self.columnIdAuthor, H5PIntegration.user.id, H5PIntegration.user.name || '');
|
||||
}
|
||||
else {
|
||||
self.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from source URL.
|
||||
*/
|
||||
H5PDataView.prototype.loadData = function () {
|
||||
var self = this;
|
||||
|
||||
// Throbb
|
||||
self.setMessage(H5PUtils.throbber(self.l10n.loading));
|
||||
|
||||
// Create URL
|
||||
var url = self.source;
|
||||
url += (url.indexOf('?') === -1 ? '?' : '&') + 'offset=' + self.offset + '&limit=' + self.limit;
|
||||
|
||||
// Add sorting
|
||||
if (self.order !== undefined) {
|
||||
url += '&sortBy=' + self.order.by + '&sortDir=' + self.order.dir;
|
||||
}
|
||||
|
||||
// Add filters
|
||||
var filtering;
|
||||
for (var i = 0; i < self.filterOn.length; i++) {
|
||||
if (self.filterOn[i] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filtering = true;
|
||||
url += '&filters[' + i + ']=' + encodeURIComponent(self.filterOn[i]);
|
||||
}
|
||||
|
||||
// Add facets
|
||||
for (var col in self.facets) {
|
||||
if (!self.facets.hasOwnProperty(col)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
url += '&facets[' + col + ']=' + self.facets[col].id;
|
||||
}
|
||||
|
||||
// Fire ajax request
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
cache: true,
|
||||
url: url
|
||||
}).fail(function () {
|
||||
// Error handling
|
||||
self.setMessage($('<p/>', {text: self.l10n.ajaxFailed}));
|
||||
}).done(function (data) {
|
||||
if (!data.rows.length) {
|
||||
self.setMessage($('<p/>', {text: filtering ? self.l10n.noData : self.l10n.empty}));
|
||||
}
|
||||
else {
|
||||
// Update table data
|
||||
self.updateTable(data.rows);
|
||||
}
|
||||
|
||||
// Update pagination widget
|
||||
self.updatePagination(data.num);
|
||||
|
||||
if (self.loaded !== undefined) {
|
||||
self.loaded();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Display the given message to the user.
|
||||
*
|
||||
* @param {jQuery} $message wrapper with message
|
||||
*/
|
||||
H5PDataView.prototype.setMessage = function ($message) {
|
||||
var self = this;
|
||||
|
||||
if (self.table === undefined) {
|
||||
self.$container.html('').append($message);
|
||||
}
|
||||
else {
|
||||
self.table.setBody($message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update table data.
|
||||
*
|
||||
* @param {Array} rows
|
||||
*/
|
||||
H5PDataView.prototype.updateTable = function (rows) {
|
||||
var self = this;
|
||||
|
||||
if (self.table === undefined) {
|
||||
// Clear out container
|
||||
self.$container.html('');
|
||||
|
||||
// Add filters
|
||||
self.addFilters();
|
||||
|
||||
// Add toggler for others' content
|
||||
if (H5PIntegration.user && parseInt(H5PIntegration.user.canToggleViewOthersH5PContents) > 0) {
|
||||
// canToggleViewOthersH5PContents = 1 is setting for only showing current user's contents
|
||||
self.addOthersContentToggler(parseInt(H5PIntegration.user.canToggleViewOthersH5PContents) === 1);
|
||||
}
|
||||
|
||||
// Add facets
|
||||
self.$facets = $('<div/>', {
|
||||
'class': 'h5p-facet-wrapper',
|
||||
appendTo: self.$container
|
||||
});
|
||||
|
||||
// Create new table
|
||||
self.table = new H5PUtils.Table(self.classes, self.headers);
|
||||
self.table.setHeaders(self.headers, function (order) {
|
||||
// Sorting column or direction has changed.
|
||||
self.order = order;
|
||||
self.loadData();
|
||||
}, self.order);
|
||||
self.table.appendTo(self.$container);
|
||||
}
|
||||
|
||||
// Process cell data before updating table
|
||||
for (var i = 0; i < self.headers.length; i++) {
|
||||
if (self.headers[i].facet === true) {
|
||||
// Process rows for col, expect object or array
|
||||
for (var j = 0; j < rows.length; j++) {
|
||||
rows[j][i] = self.createFacets(rows[j][i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add/update rows
|
||||
var $tbody = self.table.setRows(rows);
|
||||
|
||||
// Add event handlers for facets
|
||||
$('.h5p-facet', $tbody).click(function () {
|
||||
var $facet = $(this);
|
||||
self.filterByFacet($facet.data('col'), $facet.data('id'), $facet.text());
|
||||
}).keypress(function (event) {
|
||||
if (event.which === 32) {
|
||||
var $facet = $(this);
|
||||
self.filterByFacet($facet.data('col'), $facet.data('id'), $facet.text());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create button for adding facet to filter.
|
||||
*
|
||||
* @param (object|Array) input
|
||||
* @param number col ID of column
|
||||
*/
|
||||
H5PDataView.prototype.createFacets = function (input, col) {
|
||||
var facets = '';
|
||||
|
||||
if (input instanceof Array) {
|
||||
// Facet can be filtered on multiple values at the same time
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
if (facets !== '') {
|
||||
facets += ', ';
|
||||
}
|
||||
facets += '<span class="h5p-facet" role="button" tabindex="0" data-id="' + input[i].id + '" data-col="' + col + '">' + input[i].title + '</span>';
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Single value facet filtering
|
||||
facets += '<span class="h5p-facet" role="button" tabindex="0" data-id="' + input.id + '" data-col="' + col + '">' + input.title + '</span>';
|
||||
}
|
||||
|
||||
return facets === '' ? '—' : facets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a filter based on the given facet.
|
||||
*
|
||||
* @param number col ID of column we're filtering
|
||||
* @param number id ID to filter on
|
||||
* @param string text Human readable label for the filter
|
||||
*/
|
||||
H5PDataView.prototype.filterByFacet = function (col, id, text) {
|
||||
var self = this;
|
||||
|
||||
if (self.facets[col] !== undefined) {
|
||||
if (self.facets[col].id === id) {
|
||||
return; // Don't use the same filter again
|
||||
}
|
||||
|
||||
// Remove current filter for this col
|
||||
self.facets[col].$tag.remove();
|
||||
}
|
||||
|
||||
// Add to UI
|
||||
self.facets[col] = {
|
||||
id: id,
|
||||
'$tag': $('<span/>', {
|
||||
'class': 'h5p-facet-tag',
|
||||
text: text,
|
||||
appendTo: self.$facets,
|
||||
})
|
||||
};
|
||||
/**
|
||||
* Callback for removing filter.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var remove = function () {
|
||||
// Uncheck toggler for others' H5P contents
|
||||
if ( self.$othersContentToggler && self.facets.hasOwnProperty( self.columnIdAuthor ) ) {
|
||||
self.$othersContentToggler.prop('checked', false );
|
||||
}
|
||||
|
||||
self.facets[col].$tag.remove();
|
||||
delete self.facets[col];
|
||||
self.loadData();
|
||||
};
|
||||
|
||||
// Remove button
|
||||
$('<span/>', {
|
||||
role: 'button',
|
||||
tabindex: 0,
|
||||
appendTo: self.facets[col].$tag,
|
||||
text: self.l10n.remove,
|
||||
title: self.l10n.remove,
|
||||
on: {
|
||||
click: remove,
|
||||
keypress: function (event) {
|
||||
if (event.which === 32) {
|
||||
remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Load data with new filter
|
||||
self.loadData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update pagination widget.
|
||||
*
|
||||
* @param {Number} num size of data collection
|
||||
*/
|
||||
H5PDataView.prototype.updatePagination = function (num) {
|
||||
var self = this;
|
||||
|
||||
if (self.pagination === undefined) {
|
||||
if (self.table === undefined) {
|
||||
// No table, no pagination
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new widget
|
||||
var $pagerContainer = $('<div/>', {'class': 'h5p-pagination'});
|
||||
self.pagination = new H5PUtils.Pagination(num, self.limit, function (offset) {
|
||||
// Handle page changes in pagination widget
|
||||
self.offset = offset;
|
||||
self.loadData();
|
||||
}, self.l10n);
|
||||
|
||||
self.pagination.appendTo($pagerContainer);
|
||||
self.table.setFoot($pagerContainer);
|
||||
}
|
||||
else {
|
||||
// Update existing widget
|
||||
self.pagination.update(num, self.limit);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add filters.
|
||||
*/
|
||||
H5PDataView.prototype.addFilters = function () {
|
||||
var self = this;
|
||||
|
||||
for (var i = 0; i < self.filters.length; i++) {
|
||||
if (self.filters[i] === true) {
|
||||
// Add text input filter for col i
|
||||
self.addTextFilter(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add text filter for given col num.
|
||||
*
|
||||
* @param {Number} col
|
||||
*/
|
||||
H5PDataView.prototype.addTextFilter = function (col) {
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* Find input value and filter on it.
|
||||
* @private
|
||||
*/
|
||||
var search = function () {
|
||||
var filterOn = $input.val().replace(/^\s+|\s+$/g, '');
|
||||
if (filterOn === '') {
|
||||
filterOn = undefined;
|
||||
}
|
||||
if (filterOn !== self.filterOn[col]) {
|
||||
self.filterOn[col] = filterOn;
|
||||
self.loadData();
|
||||
}
|
||||
};
|
||||
|
||||
// Add text field for filtering
|
||||
var typing;
|
||||
var $input = $('<input/>', {
|
||||
type: 'text',
|
||||
placeholder: self.l10n.search,
|
||||
on: {
|
||||
'blur': function () {
|
||||
clearTimeout(typing);
|
||||
search();
|
||||
},
|
||||
'keyup': function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
clearTimeout(typing);
|
||||
search();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
clearTimeout(typing);
|
||||
typing = setTimeout(function () {
|
||||
search();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).appendTo(self.$container);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add toggle for others' H5P content.
|
||||
* @param {boolean} [checked=false] Initial check setting.
|
||||
*/
|
||||
H5PDataView.prototype.addOthersContentToggler = function (checked) {
|
||||
var self = this;
|
||||
|
||||
checked = (typeof checked === 'undefined') ? false : checked;
|
||||
|
||||
// Checkbox
|
||||
this.$othersContentToggler = $('<input/>', {
|
||||
type: 'checkbox',
|
||||
'class': 'h5p-others-contents-toggler',
|
||||
'id': 'h5p-others-contents-toggler',
|
||||
'checked': checked,
|
||||
'click': function () {
|
||||
if ( this.checked ) {
|
||||
// Add filter on current user
|
||||
self.filterByFacet( self.columnIdAuthor, H5PIntegration.user.id, H5PIntegration.user.name );
|
||||
}
|
||||
else {
|
||||
// Remove facet indicator and reload full data view
|
||||
if ( self.facets.hasOwnProperty( self.columnIdAuthor ) && self.facets[self.columnIdAuthor].$tag ) {
|
||||
self.facets[self.columnIdAuthor].$tag.remove();
|
||||
}
|
||||
delete self.facets[self.columnIdAuthor];
|
||||
self.loadData();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Label
|
||||
var $label = $('<label>', {
|
||||
'class': 'h5p-others-contents-toggler-label',
|
||||
'text': this.l10n.showOwnContentOnly,
|
||||
'for': 'h5p-others-contents-toggler'
|
||||
}).prepend(this.$othersContentToggler);
|
||||
|
||||
$('<div>', {
|
||||
'class': 'h5p-others-contents-toggler-wrapper'
|
||||
}).append($label)
|
||||
.appendTo(this.$container);
|
||||
};
|
||||
|
||||
return H5PDataView;
|
||||
})(H5P.jQuery);
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Utility that makes it possible to hide fields when a checkbox is unchecked
|
||||
*/
|
||||
(function ($) {
|
||||
function setupHiding() {
|
||||
var $toggler = $(this);
|
||||
|
||||
// Getting the field which should be hidden:
|
||||
var $subject = $($toggler.data('h5p-visibility-subject-selector'));
|
||||
|
||||
var toggle = function () {
|
||||
$subject.toggle($toggler.is(':checked'));
|
||||
};
|
||||
|
||||
$toggler.change(toggle);
|
||||
toggle();
|
||||
}
|
||||
|
||||
function setupRevealing() {
|
||||
var $button = $(this);
|
||||
|
||||
// Getting the field which should have the value:
|
||||
var $input = $('#' + $button.data('control'));
|
||||
|
||||
if (!$input.data('value')) {
|
||||
$button.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup button action
|
||||
var revealed = false;
|
||||
var text = $button.html();
|
||||
$button.click(function () {
|
||||
if (revealed) {
|
||||
$input.val('');
|
||||
$button.html(text);
|
||||
revealed = false;
|
||||
}
|
||||
else {
|
||||
$input.val($input.data('value'));
|
||||
$button.html($button.data('hide'));
|
||||
revealed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// Get the checkboxes making other fields being hidden:
|
||||
$('.h5p-visibility-toggler').each(setupHiding);
|
||||
|
||||
// Get the buttons making other fields have hidden values:
|
||||
$('.h5p-reveal-value').each(setupRevealing);
|
||||
});
|
||||
})(H5P.jQuery);
|
|
@ -0,0 +1,75 @@
|
|||
/*jshint multistr: true */
|
||||
|
||||
/**
|
||||
* Converts old script tag embed to iframe
|
||||
*/
|
||||
var H5POldEmbed = H5POldEmbed || (function () {
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var resizer = false;
|
||||
|
||||
/**
|
||||
* Loads the resizing script
|
||||
*/
|
||||
var loadResizer = function (url) {
|
||||
var data, callback = 'H5POldEmbed';
|
||||
resizer = true;
|
||||
|
||||
// Callback for when content data is loaded.
|
||||
window[callback] = function (content) {
|
||||
// Add resizing script to head
|
||||
var resizer = document.createElement('script');
|
||||
resizer.src = content;
|
||||
head.appendChild(resizer);
|
||||
|
||||
// Clean up
|
||||
head.removeChild(data);
|
||||
delete window[callback];
|
||||
};
|
||||
|
||||
// Create data script
|
||||
data = document.createElement('script');
|
||||
data.src = url + (url.indexOf('?') === -1 ? '?' : '&') + 'callback=' + callback;
|
||||
head.appendChild(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaced script tag with iframe
|
||||
*/
|
||||
var addIframe = function (script) {
|
||||
// Add iframe
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.src = script.getAttribute('data-h5p');
|
||||
iframe.frameBorder = false;
|
||||
iframe.allowFullscreen = true;
|
||||
var parent = script.parentNode;
|
||||
parent.insertBefore(iframe, script);
|
||||
parent.removeChild(script);
|
||||
};
|
||||
|
||||
/**
|
||||
* Go throught all script tags with the data-h5p attribute and load content.
|
||||
*/
|
||||
function H5POldEmbed() {
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var h5ps = []; // Use seperate array since scripts grow in size.
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var script = scripts[i];
|
||||
if (script.src.indexOf('/h5p-resizer.js') !== -1) {
|
||||
resizer = true;
|
||||
}
|
||||
else if (script.hasAttribute('data-h5p')) {
|
||||
h5ps.push(script);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < h5ps.length; i++) {
|
||||
if (!resizer) {
|
||||
loadResizer(h5ps[i].getAttribute('data-h5p'));
|
||||
}
|
||||
addIframe(h5ps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return H5POldEmbed;
|
||||
})();
|
||||
|
||||
new H5POldEmbed();
|
|
@ -0,0 +1,258 @@
|
|||
var H5P = window.H5P = window.H5P || {};
|
||||
|
||||
/**
|
||||
* The Event class for the EventDispatcher.
|
||||
*
|
||||
* @class
|
||||
* @param {string} type
|
||||
* @param {*} data
|
||||
* @param {Object} [extras]
|
||||
* @param {boolean} [extras.bubbles]
|
||||
* @param {boolean} [extras.external]
|
||||
*/
|
||||
H5P.Event = function (type, data, extras) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
var bubbles = false;
|
||||
|
||||
// Is this an external event?
|
||||
var external = false;
|
||||
|
||||
// Is this event scheduled to be sent externally?
|
||||
var scheduledForExternal = false;
|
||||
|
||||
if (extras === undefined) {
|
||||
extras = {};
|
||||
}
|
||||
if (extras.bubbles === true) {
|
||||
bubbles = true;
|
||||
}
|
||||
if (extras.external === true) {
|
||||
external = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent this event from bubbling up to parent
|
||||
*/
|
||||
this.preventBubbling = function () {
|
||||
bubbles = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get bubbling status
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if bubbling false otherwise
|
||||
*/
|
||||
this.getBubbles = function () {
|
||||
return bubbles;
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to schedule an event for externalDispatcher
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if external and not already scheduled, otherwise false
|
||||
*/
|
||||
this.scheduleForExternal = function () {
|
||||
if (external && !scheduledForExternal) {
|
||||
scheduledForExternal = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback type for event listeners.
|
||||
*
|
||||
* @callback H5P.EventCallback
|
||||
* @param {H5P.Event} event
|
||||
*/
|
||||
|
||||
H5P.EventDispatcher = (function () {
|
||||
|
||||
/**
|
||||
* The base of the event system.
|
||||
* Inherit this class if you want your H5P to dispatch events.
|
||||
*
|
||||
* @class
|
||||
* @memberof H5P
|
||||
*/
|
||||
function EventDispatcher() {
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* Keep track of listeners for each event.
|
||||
*
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
var triggers = {};
|
||||
|
||||
/**
|
||||
* Add new event listener.
|
||||
*
|
||||
* @throws {TypeError}
|
||||
* listener must be a function
|
||||
* @param {string} type
|
||||
* Event type
|
||||
* @param {H5P.EventCallback} listener
|
||||
* Event listener
|
||||
* @param {Object} [thisArg]
|
||||
* Optionally specify the this value when calling listener.
|
||||
*/
|
||||
this.on = function (type, listener, thisArg) {
|
||||
if (typeof listener !== 'function') {
|
||||
throw TypeError('listener must be a function');
|
||||
}
|
||||
|
||||
// Trigger event before adding to avoid recursion
|
||||
self.trigger('newListener', {'type': type, 'listener': listener});
|
||||
|
||||
var trigger = {'listener': listener, 'thisArg': thisArg};
|
||||
if (!triggers[type]) {
|
||||
// First
|
||||
triggers[type] = [trigger];
|
||||
}
|
||||
else {
|
||||
// Append
|
||||
triggers[type].push(trigger);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add new event listener that will be fired only once.
|
||||
*
|
||||
* @throws {TypeError}
|
||||
* listener must be a function
|
||||
* @param {string} type
|
||||
* Event type
|
||||
* @param {H5P.EventCallback} listener
|
||||
* Event listener
|
||||
* @param {Object} thisArg
|
||||
* Optionally specify the this value when calling listener.
|
||||
*/
|
||||
this.once = function (type, listener, thisArg) {
|
||||
if (!(listener instanceof Function)) {
|
||||
throw TypeError('listener must be a function');
|
||||
}
|
||||
|
||||
var once = function (event) {
|
||||
self.off(event.type, once);
|
||||
listener.call(this, event);
|
||||
};
|
||||
|
||||
self.on(type, once, thisArg);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove event listener.
|
||||
* If no listener is specified, all listeners will be removed.
|
||||
*
|
||||
* @throws {TypeError}
|
||||
* listener must be a function
|
||||
* @param {string} type
|
||||
* Event type
|
||||
* @param {H5P.EventCallback} listener
|
||||
* Event listener
|
||||
*/
|
||||
this.off = function (type, listener) {
|
||||
if (listener !== undefined && !(listener instanceof Function)) {
|
||||
throw TypeError('listener must be a function');
|
||||
}
|
||||
|
||||
if (triggers[type] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listener === undefined) {
|
||||
// Remove all listeners
|
||||
delete triggers[type];
|
||||
self.trigger('removeListener', type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find specific listener
|
||||
for (var i = 0; i < triggers[type].length; i++) {
|
||||
if (triggers[type][i].listener === listener) {
|
||||
triggers[type].splice(i, 1);
|
||||
self.trigger('removeListener', type, {'listener': listener});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up empty arrays
|
||||
if (!triggers[type].length) {
|
||||
delete triggers[type];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to call all event listeners for the given event type.
|
||||
*
|
||||
* @private
|
||||
* @param {string} Event type
|
||||
*/
|
||||
var call = function (type, event) {
|
||||
if (triggers[type] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clone array (prevents triggers from being modified during the event)
|
||||
var handlers = triggers[type].slice();
|
||||
|
||||
// Call all listeners
|
||||
for (var i = 0; i < handlers.length; i++) {
|
||||
var trigger = handlers[i];
|
||||
var thisArg = (trigger.thisArg ? trigger.thisArg : this);
|
||||
trigger.listener.call(thisArg, event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch event.
|
||||
*
|
||||
* @param {string|H5P.Event} event
|
||||
* Event object or event type as string
|
||||
* @param {*} [eventData]
|
||||
* Custom event data(used when event type as string is used as first
|
||||
* argument).
|
||||
* @param {Object} [extras]
|
||||
* @param {boolean} [extras.bubbles]
|
||||
* @param {boolean} [extras.external]
|
||||
*/
|
||||
this.trigger = function (event, eventData, extras) {
|
||||
if (event === undefined) {
|
||||
return;
|
||||
}
|
||||
if (event instanceof String || typeof event === 'string') {
|
||||
event = new H5P.Event(event, eventData, extras);
|
||||
}
|
||||
else if (eventData !== undefined) {
|
||||
event.data = eventData;
|
||||
}
|
||||
|
||||
// Check to see if this event should go externally after all triggering and bubbling is done
|
||||
var scheduledForExternal = event.scheduleForExternal();
|
||||
|
||||
// Call all listeners
|
||||
call.call(this, event.type, event);
|
||||
|
||||
// Call all * listeners
|
||||
call.call(this, '*', event);
|
||||
|
||||
// Bubble
|
||||
if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher &&
|
||||
(self.parent.trigger instanceof Function || typeof self.parent.trigger === 'function')) {
|
||||
self.parent.trigger(event);
|
||||
}
|
||||
|
||||
if (scheduledForExternal) {
|
||||
H5P.externalDispatcher.trigger.call(this, event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return EventDispatcher;
|
||||
})();
|
|
@ -0,0 +1,297 @@
|
|||
/* global H5PAdminIntegration H5PUtils */
|
||||
var H5PLibraryDetails = H5PLibraryDetails || {};
|
||||
|
||||
(function ($) {
|
||||
|
||||
H5PLibraryDetails.PAGER_SIZE = 20;
|
||||
/**
|
||||
* Initializing
|
||||
*/
|
||||
H5PLibraryDetails.init = function () {
|
||||
H5PLibraryDetails.$adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector);
|
||||
H5PLibraryDetails.library = H5PAdminIntegration.libraryInfo;
|
||||
|
||||
// currentContent holds the current list if data (relevant for filtering)
|
||||
H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content;
|
||||
|
||||
// The current page index (for pager)
|
||||
H5PLibraryDetails.currentPage = 0;
|
||||
|
||||
// The current filter
|
||||
H5PLibraryDetails.currentFilter = '';
|
||||
|
||||
// We cache the filtered results, so we don't have to do unneccessary searches
|
||||
H5PLibraryDetails.filterCache = [];
|
||||
|
||||
// Append library info
|
||||
H5PLibraryDetails.$adminContainer.append(H5PLibraryDetails.createLibraryInfo());
|
||||
|
||||
// Append node list
|
||||
H5PLibraryDetails.$adminContainer.append(H5PLibraryDetails.createContentElement());
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the library details view
|
||||
*/
|
||||
H5PLibraryDetails.createLibraryInfo = function () {
|
||||
var $libraryInfo = $('<div class="h5p-library-info"></div>');
|
||||
|
||||
$.each(H5PLibraryDetails.library.info, function (title, value) {
|
||||
$libraryInfo.append(H5PUtils.createLabeledField(title, value));
|
||||
});
|
||||
|
||||
return $libraryInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the content list with searching and paging
|
||||
*/
|
||||
H5PLibraryDetails.createContentElement = function () {
|
||||
if (H5PLibraryDetails.library.notCached !== undefined) {
|
||||
return H5PUtils.getRebuildCache(H5PLibraryDetails.library.notCached);
|
||||
}
|
||||
|
||||
if (H5PLibraryDetails.currentContent === undefined) {
|
||||
H5PLibraryDetails.$content = $('<div class="h5p-content empty">' + H5PLibraryDetails.library.translations.noContent + '</div>');
|
||||
}
|
||||
else {
|
||||
H5PLibraryDetails.$content = $('<div class="h5p-content"><h3>' + H5PLibraryDetails.library.translations.contentHeader + '</h3></div>');
|
||||
H5PLibraryDetails.createSearchElement();
|
||||
H5PLibraryDetails.createPageSizeSelector();
|
||||
H5PLibraryDetails.createContentTable();
|
||||
H5PLibraryDetails.createPagerElement();
|
||||
return H5PLibraryDetails.$content;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the content list
|
||||
*/
|
||||
H5PLibraryDetails.createContentTable = function () {
|
||||
// Remove it if it exists:
|
||||
if (H5PLibraryDetails.$contentTable) {
|
||||
H5PLibraryDetails.$contentTable.remove();
|
||||
}
|
||||
|
||||
H5PLibraryDetails.$contentTable = H5PUtils.createTable();
|
||||
|
||||
var i = (H5PLibraryDetails.currentPage*H5PLibraryDetails.PAGER_SIZE);
|
||||
var lastIndex = (i+H5PLibraryDetails.PAGER_SIZE);
|
||||
|
||||
if (lastIndex > H5PLibraryDetails.currentContent.length) {
|
||||
lastIndex = H5PLibraryDetails.currentContent.length;
|
||||
}
|
||||
for (; i<lastIndex; i++) {
|
||||
var content = H5PLibraryDetails.currentContent[i];
|
||||
H5PLibraryDetails.$contentTable.append(H5PUtils.createTableRow(['<a href="' + content.url + '">' + content.title + '</a>']));
|
||||
}
|
||||
|
||||
// Appends it to the browser DOM
|
||||
H5PLibraryDetails.$contentTable.insertAfter(H5PLibraryDetails.$search);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the pager element on the bottom of the list
|
||||
*/
|
||||
H5PLibraryDetails.createPagerElement = function () {
|
||||
H5PLibraryDetails.$previous = $('<button type="button" class="previous h5p-admin"><</button>');
|
||||
H5PLibraryDetails.$next = $('<button type="button" class="next h5p-admin">></button>');
|
||||
|
||||
H5PLibraryDetails.$previous.on('click', function () {
|
||||
if (H5PLibraryDetails.$previous.hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
H5PLibraryDetails.currentPage--;
|
||||
H5PLibraryDetails.updatePager();
|
||||
H5PLibraryDetails.createContentTable();
|
||||
});
|
||||
|
||||
H5PLibraryDetails.$next.on('click', function () {
|
||||
if (H5PLibraryDetails.$next.hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
H5PLibraryDetails.currentPage++;
|
||||
H5PLibraryDetails.updatePager();
|
||||
H5PLibraryDetails.createContentTable();
|
||||
});
|
||||
|
||||
// This is the Page x of y widget:
|
||||
H5PLibraryDetails.$pagerInfo = $('<span class="pager-info"></span>');
|
||||
|
||||
H5PLibraryDetails.$pager = $('<div class="h5p-content-pager"></div>').append(H5PLibraryDetails.$previous, H5PLibraryDetails.$pagerInfo, H5PLibraryDetails.$next);
|
||||
H5PLibraryDetails.$content.append(H5PLibraryDetails.$pager);
|
||||
|
||||
H5PLibraryDetails.$pagerInfo.on('click', function () {
|
||||
var width = H5PLibraryDetails.$pagerInfo.innerWidth();
|
||||
H5PLibraryDetails.$pagerInfo.hide();
|
||||
|
||||
// User has updated the pageNumber
|
||||
var pageNumerUpdated = function () {
|
||||
var newPageNum = $gotoInput.val()-1;
|
||||
var intRegex = /^\d+$/;
|
||||
|
||||
$goto.remove();
|
||||
H5PLibraryDetails.$pagerInfo.css({display: 'inline-block'});
|
||||
|
||||
// Check if input value is valid, and that it has actually changed
|
||||
if (!(intRegex.test(newPageNum) && newPageNum >= 0 && newPageNum < H5PLibraryDetails.getNumPages() && newPageNum != H5PLibraryDetails.currentPage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
H5PLibraryDetails.currentPage = newPageNum;
|
||||
H5PLibraryDetails.updatePager();
|
||||
H5PLibraryDetails.createContentTable();
|
||||
};
|
||||
|
||||
// We create an input box where the user may type in the page number
|
||||
// he wants to be displayed.
|
||||
// Reson for doing this is when user has ten-thousands of elements in list,
|
||||
// this is the easiest way of getting to a specified page
|
||||
var $gotoInput = $('<input/>', {
|
||||
type: 'number',
|
||||
min : 1,
|
||||
max: H5PLibraryDetails.getNumPages(),
|
||||
on: {
|
||||
// Listen to blur, and the enter-key:
|
||||
'blur': pageNumerUpdated,
|
||||
'keyup': function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
pageNumerUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).css({width: width});
|
||||
var $goto = $('<span/>', {
|
||||
'class': 'h5p-pager-goto'
|
||||
}).css({width: width}).append($gotoInput).insertAfter(H5PLibraryDetails.$pagerInfo);
|
||||
|
||||
$gotoInput.focus();
|
||||
});
|
||||
|
||||
H5PLibraryDetails.updatePager();
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates number of pages
|
||||
*/
|
||||
H5PLibraryDetails.getNumPages = function () {
|
||||
return Math.ceil(H5PLibraryDetails.currentContent.length / H5PLibraryDetails.PAGER_SIZE);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the pager text, and enables/disables the next and previous buttons as needed
|
||||
*/
|
||||
H5PLibraryDetails.updatePager = function () {
|
||||
H5PLibraryDetails.$pagerInfo.css({display: 'inline-block'});
|
||||
|
||||
if (H5PLibraryDetails.getNumPages() > 0) {
|
||||
var message = H5PUtils.translateReplace(H5PLibraryDetails.library.translations.pageXOfY, {
|
||||
'$x': (H5PLibraryDetails.currentPage+1),
|
||||
'$y': H5PLibraryDetails.getNumPages()
|
||||
});
|
||||
H5PLibraryDetails.$pagerInfo.html(message);
|
||||
}
|
||||
else {
|
||||
H5PLibraryDetails.$pagerInfo.html('');
|
||||
}
|
||||
|
||||
H5PLibraryDetails.$previous.toggleClass('disabled', H5PLibraryDetails.currentPage <= 0);
|
||||
H5PLibraryDetails.$next.toggleClass('disabled', H5PLibraryDetails.currentContent.length < (H5PLibraryDetails.currentPage+1)*H5PLibraryDetails.PAGER_SIZE);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the search element
|
||||
*/
|
||||
H5PLibraryDetails.createSearchElement = function () {
|
||||
|
||||
H5PLibraryDetails.$search = $('<div class="h5p-content-search"><input placeholder="' + H5PLibraryDetails.library.translations.filterPlaceholder + '" type="search"></div>');
|
||||
|
||||
var performSeach = function () {
|
||||
var searchString = $('.h5p-content-search > input').val();
|
||||
|
||||
// If search string same as previous, just do nothing
|
||||
if (H5PLibraryDetails.currentFilter === searchString) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchString.trim().length === 0) {
|
||||
// If empty search, use the complete list
|
||||
H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content;
|
||||
}
|
||||
else if (H5PLibraryDetails.filterCache[searchString]) {
|
||||
// If search is cached, no need to filter
|
||||
H5PLibraryDetails.currentContent = H5PLibraryDetails.filterCache[searchString];
|
||||
}
|
||||
else {
|
||||
var listToFilter = H5PLibraryDetails.library.content;
|
||||
|
||||
// Check if we can filter the already filtered results (for performance)
|
||||
if (searchString.length > 1 && H5PLibraryDetails.currentFilter === searchString.substr(0, H5PLibraryDetails.currentFilter.length)) {
|
||||
listToFilter = H5PLibraryDetails.currentContent;
|
||||
}
|
||||
H5PLibraryDetails.currentContent = $.grep(listToFilter, function (content) {
|
||||
return content.title && content.title.match(new RegExp(searchString, 'i'));
|
||||
});
|
||||
}
|
||||
|
||||
H5PLibraryDetails.currentFilter = searchString;
|
||||
// Cache the current result
|
||||
H5PLibraryDetails.filterCache[searchString] = H5PLibraryDetails.currentContent;
|
||||
H5PLibraryDetails.currentPage = 0;
|
||||
H5PLibraryDetails.createContentTable();
|
||||
|
||||
// Display search results:
|
||||
if (H5PLibraryDetails.$searchResults) {
|
||||
H5PLibraryDetails.$searchResults.remove();
|
||||
}
|
||||
if (searchString.trim().length > 0) {
|
||||
H5PLibraryDetails.$searchResults = $('<span class="h5p-admin-search-results">' + H5PLibraryDetails.currentContent.length + ' hits on ' + H5PLibraryDetails.currentFilter + '</span>');
|
||||
H5PLibraryDetails.$search.append(H5PLibraryDetails.$searchResults);
|
||||
}
|
||||
H5PLibraryDetails.updatePager();
|
||||
};
|
||||
|
||||
var inputTimer;
|
||||
$('input', H5PLibraryDetails.$search).on('change keypress paste input', function () {
|
||||
// Here we start the filtering
|
||||
// We wait at least 500 ms after last input to perform search
|
||||
if (inputTimer) {
|
||||
clearTimeout(inputTimer);
|
||||
}
|
||||
|
||||
inputTimer = setTimeout( function () {
|
||||
performSeach();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
H5PLibraryDetails.$content.append(H5PLibraryDetails.$search);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the page size selector
|
||||
*/
|
||||
H5PLibraryDetails.createPageSizeSelector = function () {
|
||||
H5PLibraryDetails.$search.append('<div class="h5p-admin-pager-size-selector">' + H5PLibraryDetails.library.translations.pageSizeSelectorLabel + ':<span data-page-size="10">10</span><span class="selected" data-page-size="20">20</span><span data-page-size="50">50</span><span data-page-size="100">100</span><span data-page-size="200">200</span></div>');
|
||||
|
||||
// Listen to clicks on the page size selector:
|
||||
$('.h5p-admin-pager-size-selector > span', H5PLibraryDetails.$search).on('click', function () {
|
||||
H5PLibraryDetails.PAGER_SIZE = $(this).data('page-size');
|
||||
$('.h5p-admin-pager-size-selector > span', H5PLibraryDetails.$search).removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
H5PLibraryDetails.currentPage = 0;
|
||||
H5PLibraryDetails.createContentTable();
|
||||
H5PLibraryDetails.updatePager();
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize me:
|
||||
$(document).ready(function () {
|
||||
if (!H5PLibraryDetails.initialized) {
|
||||
H5PLibraryDetails.initialized = true;
|
||||
H5PLibraryDetails.init();
|
||||
}
|
||||
});
|
||||
|
||||
})(H5P.jQuery);
|
|
@ -0,0 +1,140 @@
|
|||
/* global H5PAdminIntegration H5PUtils */
|
||||
var H5PLibraryList = H5PLibraryList || {};
|
||||
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* Initializing
|
||||
*/
|
||||
H5PLibraryList.init = function () {
|
||||
var $adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector).html('');
|
||||
|
||||
var libraryList = H5PAdminIntegration.libraryList;
|
||||
if (libraryList.notCached) {
|
||||
$adminContainer.append(H5PUtils.getRebuildCache(libraryList.notCached));
|
||||
}
|
||||
|
||||
// Create library list
|
||||
$adminContainer.append(H5PLibraryList.createLibraryList(H5PAdminIntegration.libraryList));
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the library list
|
||||
*
|
||||
* @param {object} libraries List of libraries and headers
|
||||
*/
|
||||
H5PLibraryList.createLibraryList = function (libraries) {
|
||||
var t = H5PAdminIntegration.l10n;
|
||||
if (libraries.listData === undefined || libraries.listData.length === 0) {
|
||||
return $('<div>' + t.NA + '</div>');
|
||||
}
|
||||
|
||||
// Create table
|
||||
var $table = H5PUtils.createTable(libraries.listHeaders);
|
||||
$table.addClass('libraries');
|
||||
|
||||
// Add libraries
|
||||
$.each (libraries.listData, function (index, library) {
|
||||
var $libraryRow = H5PUtils.createTableRow([
|
||||
library.title,
|
||||
'<input class="h5p-admin-restricted" type="checkbox"/>',
|
||||
{
|
||||
text: library.numContent,
|
||||
class: 'h5p-admin-center'
|
||||
},
|
||||
{
|
||||
text: library.numContentDependencies,
|
||||
class: 'h5p-admin-center'
|
||||
},
|
||||
{
|
||||
text: library.numLibraryDependencies,
|
||||
class: 'h5p-admin-center'
|
||||
},
|
||||
'<div class="h5p-admin-buttons-wrapper">' +
|
||||
'<button class="h5p-admin-upgrade-library"></button>' +
|
||||
(library.detailsUrl ? '<button class="h5p-admin-view-library" title="' + t.viewLibrary + '"></button>' : '') +
|
||||
(library.deleteUrl ? '<button class="h5p-admin-delete-library"></button>' : '') +
|
||||
'</div>'
|
||||
]);
|
||||
|
||||
H5PLibraryList.addRestricted($('.h5p-admin-restricted', $libraryRow), library.restrictedUrl, library.restricted);
|
||||
|
||||
var hasContent = !(library.numContent === '' || library.numContent === 0);
|
||||
if (library.upgradeUrl === null) {
|
||||
$('.h5p-admin-upgrade-library', $libraryRow).remove();
|
||||
}
|
||||
else if (library.upgradeUrl === false || !hasContent) {
|
||||
$('.h5p-admin-upgrade-library', $libraryRow).attr('disabled', true);
|
||||
}
|
||||
else {
|
||||
$('.h5p-admin-upgrade-library', $libraryRow).attr('title', t.upgradeLibrary).click(function () {
|
||||
window.location.href = library.upgradeUrl;
|
||||
});
|
||||
}
|
||||
|
||||
// Open details view when clicked
|
||||
$('.h5p-admin-view-library', $libraryRow).on('click', function () {
|
||||
window.location.href = library.detailsUrl;
|
||||
});
|
||||
|
||||
var $deleteButton = $('.h5p-admin-delete-library', $libraryRow);
|
||||
if (libraries.notCached !== undefined ||
|
||||
hasContent ||
|
||||
(library.numContentDependencies !== '' &&
|
||||
library.numContentDependencies !== 0) ||
|
||||
(library.numLibraryDependencies !== '' &&
|
||||
library.numLibraryDependencies !== 0)) {
|
||||
// Disabled delete if content.
|
||||
$deleteButton.attr('disabled', true);
|
||||
}
|
||||
else {
|
||||
// Go to delete page om click.
|
||||
$deleteButton.attr('title', t.deleteLibrary).on('click', function () {
|
||||
window.location.href = library.deleteUrl;
|
||||
});
|
||||
}
|
||||
|
||||
$table.append($libraryRow);
|
||||
});
|
||||
|
||||
return $table;
|
||||
};
|
||||
|
||||
H5PLibraryList.addRestricted = function ($checkbox, url, selected) {
|
||||
if (selected === null) {
|
||||
$checkbox.remove();
|
||||
}
|
||||
else {
|
||||
$checkbox.change(function () {
|
||||
$checkbox.attr('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: url,
|
||||
cache: false
|
||||
}).fail(function () {
|
||||
$checkbox.attr('disabled', false);
|
||||
|
||||
// Reset
|
||||
$checkbox.attr('checked', !$checkbox.is(':checked'));
|
||||
}).done(function (result) {
|
||||
url = result.url;
|
||||
$checkbox.attr('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
if (selected) {
|
||||
$checkbox.attr('checked', true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize me:
|
||||
$(document).ready(function () {
|
||||
if (!H5PLibraryList.initialized) {
|
||||
H5PLibraryList.initialized = true;
|
||||
H5PLibraryList.init();
|
||||
}
|
||||
});
|
||||
|
||||
})(H5P.jQuery);
|
|
@ -0,0 +1,131 @@
|
|||
// H5P iframe Resizer
|
||||
(function () {
|
||||
if (!window.postMessage || !window.addEventListener || window.h5pResizerInitialized) {
|
||||
return; // Not supported
|
||||
}
|
||||
window.h5pResizerInitialized = true;
|
||||
|
||||
// Map actions to handlers
|
||||
var actionHandlers = {};
|
||||
|
||||
/**
|
||||
* Prepare iframe resize.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} iframe Element
|
||||
* @param {Object} data Payload
|
||||
* @param {Function} respond Send a response to the iframe
|
||||
*/
|
||||
actionHandlers.hello = function (iframe, data, respond) {
|
||||
// Make iframe responsive
|
||||
iframe.style.width = '100%';
|
||||
|
||||
// Bugfix for Chrome: Force update of iframe width. If this is not done the
|
||||
// document size may not be updated before the content resizes.
|
||||
iframe.getBoundingClientRect();
|
||||
|
||||
// Tell iframe that it needs to resize when our window resizes
|
||||
var resize = function () {
|
||||
if (iframe.contentWindow) {
|
||||
// Limit resize calls to avoid flickering
|
||||
respond('resize');
|
||||
}
|
||||
else {
|
||||
// Frame is gone, unregister.
|
||||
window.removeEventListener('resize', resize);
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', resize, false);
|
||||
|
||||
// Respond to let the iframe know we can resize it
|
||||
respond('hello');
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare iframe resize.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} iframe Element
|
||||
* @param {Object} data Payload
|
||||
* @param {Function} respond Send a response to the iframe
|
||||
*/
|
||||
actionHandlers.prepareResize = function (iframe, data, respond) {
|
||||
// Do not resize unless page and scrolling differs
|
||||
if (iframe.clientHeight !== data.scrollHeight ||
|
||||
data.scrollHeight !== data.clientHeight) {
|
||||
|
||||
// Reset iframe height, in case content has shrinked.
|
||||
iframe.style.height = data.clientHeight + 'px';
|
||||
respond('resizePrepared');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize parent and iframe to desired height.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} iframe Element
|
||||
* @param {Object} data Payload
|
||||
* @param {Function} respond Send a response to the iframe
|
||||
*/
|
||||
actionHandlers.resize = function (iframe, data) {
|
||||
// Resize iframe so all content is visible. Use scrollHeight to make sure we get everything
|
||||
iframe.style.height = data.scrollHeight + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
* Keyup event handler. Exits full screen on escape.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
var escape = function (event) {
|
||||
if (event.keyCode === 27) {
|
||||
exitFullScreen();
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for messages from iframes
|
||||
window.addEventListener('message', function receiveMessage(event) {
|
||||
if (event.data.context !== 'h5p') {
|
||||
return; // Only handle h5p requests.
|
||||
}
|
||||
|
||||
// Find out who sent the message
|
||||
var iframe, iframes = document.getElementsByTagName('iframe');
|
||||
for (var i = 0; i < iframes.length; i++) {
|
||||
if (iframes[i].contentWindow === event.source) {
|
||||
iframe = iframes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!iframe) {
|
||||
return; // Cannot find sender
|
||||
}
|
||||
|
||||
// Find action handler handler
|
||||
if (actionHandlers[event.data.action]) {
|
||||
actionHandlers[event.data.action](iframe, event.data, function respond(action, data) {
|
||||
if (data === undefined) {
|
||||
data = {};
|
||||
}
|
||||
data.action = action;
|
||||
data.context = 'h5p';
|
||||
event.source.postMessage(data, event.origin);
|
||||
});
|
||||
}
|
||||
}, false);
|
||||
|
||||
// Let h5p iframes know we're ready!
|
||||
var iframes = document.getElementsByTagName('iframe');
|
||||
var ready = {
|
||||
context: 'h5p',
|
||||
action: 'ready'
|
||||
};
|
||||
for (var i = 0; i < iframes.length; i++) {
|
||||
if (iframes[i].src.indexOf('h5p') !== -1) {
|
||||
iframes[i].contentWindow.postMessage(ready, '*');
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
|
@ -0,0 +1,506 @@
|
|||
/* global H5PAdminIntegration*/
|
||||
var H5PUtils = H5PUtils || {};
|
||||
|
||||
(function ($) {
|
||||
/**
|
||||
* Generic function for creating a table including the headers
|
||||
*
|
||||
* @param {array} headers List of headers
|
||||
*/
|
||||
H5PUtils.createTable = function (headers) {
|
||||
var $table = $('<table class="h5p-admin-table' + (H5PAdminIntegration.extraTableClasses !== undefined ? ' ' + H5PAdminIntegration.extraTableClasses : '') + '"></table>');
|
||||
|
||||
if (headers) {
|
||||
var $thead = $('<thead></thead>');
|
||||
var $tr = $('<tr></tr>');
|
||||
|
||||
$.each(headers, function (index, value) {
|
||||
if (!(value instanceof Object)) {
|
||||
value = {
|
||||
html: value
|
||||
};
|
||||
}
|
||||
|
||||
$('<th/>', value).appendTo($tr);
|
||||
});
|
||||
|
||||
$table.append($thead.append($tr));
|
||||
}
|
||||
|
||||
return $table;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic function for creating a table row
|
||||
*
|
||||
* @param {array} rows Value list. Object name is used as class name in <TD>
|
||||
*/
|
||||
H5PUtils.createTableRow = function (rows) {
|
||||
var $tr = $('<tr></tr>');
|
||||
|
||||
$.each(rows, function (index, value) {
|
||||
if (!(value instanceof Object)) {
|
||||
value = {
|
||||
html: value
|
||||
};
|
||||
}
|
||||
|
||||
$('<td/>', value).appendTo($tr);
|
||||
});
|
||||
|
||||
return $tr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic function for creating a field containing label and value
|
||||
*
|
||||
* @param {string} label The label displayed in front of the value
|
||||
* @param {string} value The value
|
||||
*/
|
||||
H5PUtils.createLabeledField = function (label, value) {
|
||||
var $field = $('<div class="h5p-labeled-field"></div>');
|
||||
|
||||
$field.append('<div class="h5p-label">' + label + '</div>');
|
||||
$field.append('<div class="h5p-value">' + value + '</div>');
|
||||
|
||||
return $field;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces placeholder fields in translation strings
|
||||
*
|
||||
* @param {string} template The translation template string in the following format: "$name is a $sex"
|
||||
* @param {array} replacors An js object with key and values. Eg: {'$name': 'Frode', '$sex': 'male'}
|
||||
*/
|
||||
H5PUtils.translateReplace = function (template, replacors) {
|
||||
$.each(replacors, function (key, value) {
|
||||
template = template.replace(new RegExp('\\'+key, 'g'), value);
|
||||
});
|
||||
return template;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get throbber with given text.
|
||||
*
|
||||
* @param {String} text
|
||||
* @returns {$}
|
||||
*/
|
||||
H5PUtils.throbber = function (text) {
|
||||
return $('<div/>', {
|
||||
class: 'h5p-throbber',
|
||||
text: text
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes it possbile to rebuild all content caches from admin UI.
|
||||
* @param {Object} notCached
|
||||
* @returns {$}
|
||||
*/
|
||||
H5PUtils.getRebuildCache = function (notCached) {
|
||||
var $container = $('<div class="h5p-admin-rebuild-cache"><p class="message">' + notCached.message + '</p><p class="progress">' + notCached.progress + '</p></div>');
|
||||
var $button = $('<button>' + notCached.button + '</button>').appendTo($container).click(function () {
|
||||
var $spinner = $('<div/>', {class: 'h5p-spinner'}).replaceAll($button);
|
||||
var parts = ['|', '/', '-', '\\'];
|
||||
var current = 0;
|
||||
var spinning = setInterval(function () {
|
||||
$spinner.text(parts[current]);
|
||||
current++;
|
||||
if (current === parts.length) current = 0;
|
||||
}, 100);
|
||||
|
||||
var $counter = $container.find('.progress');
|
||||
var build = function () {
|
||||
$.post(notCached.url, function (left) {
|
||||
if (left === '0') {
|
||||
clearInterval(spinning);
|
||||
$container.remove();
|
||||
location.reload();
|
||||
}
|
||||
else {
|
||||
var counter = $counter.text().split(' ');
|
||||
counter[0] = left;
|
||||
$counter.text(counter.join(' '));
|
||||
build();
|
||||
}
|
||||
});
|
||||
};
|
||||
build();
|
||||
});
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic table class with useful helpers.
|
||||
*
|
||||
* @class
|
||||
* @param {Object} classes
|
||||
* Custom html classes to use on elements.
|
||||
* e.g. {tableClass: 'fixed'}.
|
||||
*/
|
||||
H5PUtils.Table = function (classes) {
|
||||
var numCols;
|
||||
var sortByCol;
|
||||
var $sortCol;
|
||||
var sortCol;
|
||||
var sortDir;
|
||||
|
||||
// Create basic table
|
||||
var tableOptions = {};
|
||||
if (classes.table !== undefined) {
|
||||
tableOptions['class'] = classes.table;
|
||||
}
|
||||
var $table = $('<table/>', tableOptions);
|
||||
var $thead = $('<thead/>').appendTo($table);
|
||||
var $tfoot = $('<tfoot/>').appendTo($table);
|
||||
var $tbody = $('<tbody/>').appendTo($table);
|
||||
|
||||
/**
|
||||
* Add columns to given table row.
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $tr Table row
|
||||
* @param {(String|Object)} col Column properties
|
||||
* @param {Number} id Used to seperate the columns
|
||||
*/
|
||||
var addCol = function ($tr, col, id) {
|
||||
var options = {
|
||||
on: {}
|
||||
};
|
||||
|
||||
if (!(col instanceof Object)) {
|
||||
options.text = col;
|
||||
}
|
||||
else {
|
||||
if (col.text !== undefined) {
|
||||
options.text = col.text;
|
||||
}
|
||||
if (col.class !== undefined) {
|
||||
options.class = col.class;
|
||||
}
|
||||
|
||||
if (sortByCol !== undefined && col.sortable === true) {
|
||||
// Make sortable
|
||||
options.role = 'button';
|
||||
options.tabIndex = 0;
|
||||
|
||||
// This is the first sortable column, use as default sort
|
||||
if (sortCol === undefined) {
|
||||
sortCol = id;
|
||||
sortDir = 0;
|
||||
}
|
||||
|
||||
// This is the sort column
|
||||
if (sortCol === id) {
|
||||
options['class'] = 'h5p-sort';
|
||||
if (sortDir === 1) {
|
||||
options['class'] += ' h5p-reverse';
|
||||
}
|
||||
}
|
||||
|
||||
options.on.click = function () {
|
||||
sort($th, id);
|
||||
};
|
||||
options.on.keypress = function (event) {
|
||||
if ((event.charCode || event.keyCode) === 32) { // Space
|
||||
sort($th, id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Append
|
||||
var $th = $('<th>', options).appendTo($tr);
|
||||
if (sortCol === id) {
|
||||
$sortCol = $th; // Default sort column
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the UI when a column header has been clicked.
|
||||
* Triggers sorting callback.
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $th Table header
|
||||
* @param {Number} id Used to seperate the columns
|
||||
*/
|
||||
var sort = function ($th, id) {
|
||||
if (id === sortCol) {
|
||||
// Change sorting direction
|
||||
if (sortDir === 0) {
|
||||
sortDir = 1;
|
||||
$th.addClass('h5p-reverse');
|
||||
}
|
||||
else {
|
||||
sortDir = 0;
|
||||
$th.removeClass('h5p-reverse');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Change sorting column
|
||||
$sortCol.removeClass('h5p-sort').removeClass('h5p-reverse');
|
||||
$sortCol = $th.addClass('h5p-sort');
|
||||
sortCol = id;
|
||||
sortDir = 0;
|
||||
}
|
||||
|
||||
sortByCol({
|
||||
by: sortCol,
|
||||
dir: sortDir
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set table headers.
|
||||
*
|
||||
* @public
|
||||
* @param {Array} cols
|
||||
* Table header data. Can be strings or objects with options like
|
||||
* "text" and "sortable". E.g.
|
||||
* [{text: 'Col 1', sortable: true}, 'Col 2', 'Col 3']
|
||||
* @param {Function} sort Callback which is runned when sorting changes
|
||||
* @param {Object} [order]
|
||||
*/
|
||||
this.setHeaders = function (cols, sort, order) {
|
||||
numCols = cols.length;
|
||||
sortByCol = sort;
|
||||
|
||||
if (order) {
|
||||
sortCol = order.by;
|
||||
sortDir = order.dir;
|
||||
}
|
||||
|
||||
// Create new head
|
||||
var $newThead = $('<thead/>');
|
||||
var $tr = $('<tr/>').appendTo($newThead);
|
||||
for (var i = 0; i < cols.length; i++) {
|
||||
addCol($tr, cols[i], i);
|
||||
}
|
||||
|
||||
// Update DOM
|
||||
$thead.replaceWith($newThead);
|
||||
$thead = $newThead;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set table rows.
|
||||
*
|
||||
* @public
|
||||
* @param {Array} rows Table rows with cols: [[1,'hello',3],[2,'asd',6]]
|
||||
*/
|
||||
this.setRows = function (rows) {
|
||||
var $newTbody = $('<tbody/>');
|
||||
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var $tr = $('<tr/>').appendTo($newTbody);
|
||||
|
||||
for (var j = 0; j < rows[i].length; j++) {
|
||||
$('<td>', {
|
||||
html: rows[i][j]
|
||||
}).appendTo($tr);
|
||||
}
|
||||
}
|
||||
|
||||
$tbody.replaceWith($newTbody);
|
||||
$tbody = $newTbody;
|
||||
|
||||
return $tbody;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set custom table body content. This can be a message or a throbber.
|
||||
* Will cover all table columns.
|
||||
*
|
||||
* @public
|
||||
* @param {jQuery} $content Custom content
|
||||
*/
|
||||
this.setBody = function ($content) {
|
||||
var $newTbody = $('<tbody/>');
|
||||
var $tr = $('<tr/>').appendTo($newTbody);
|
||||
$('<td>', {
|
||||
colspan: numCols
|
||||
}).append($content).appendTo($tr);
|
||||
$tbody.replaceWith($newTbody);
|
||||
$tbody = $newTbody;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set custom table foot content. This can be a pagination widget.
|
||||
* Will cover all table columns.
|
||||
*
|
||||
* @public
|
||||
* @param {jQuery} $content Custom content
|
||||
*/
|
||||
this.setFoot = function ($content) {
|
||||
var $newTfoot = $('<tfoot/>');
|
||||
var $tr = $('<tr/>').appendTo($newTfoot);
|
||||
$('<td>', {
|
||||
colspan: numCols
|
||||
}).append($content).appendTo($tr);
|
||||
$tfoot.replaceWith($newTfoot);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Appends the table to the given container.
|
||||
*
|
||||
* @public
|
||||
* @param {jQuery} $container
|
||||
*/
|
||||
this.appendTo = function ($container) {
|
||||
$table.appendTo($container);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic pagination class. Creates a useful pagination widget.
|
||||
*
|
||||
* @class
|
||||
* @param {Number} num Total number of items to pagiate.
|
||||
* @param {Number} limit Number of items to dispaly per page.
|
||||
* @param {Function} goneTo
|
||||
* Callback which is fired when the user wants to go to another page.
|
||||
* @param {Object} l10n
|
||||
* Localization / translations. e.g.
|
||||
* {
|
||||
* currentPage: 'Page $current of $total',
|
||||
* nextPage: 'Next page',
|
||||
* previousPage: 'Previous page'
|
||||
* }
|
||||
*/
|
||||
H5PUtils.Pagination = function (num, limit, goneTo, l10n) {
|
||||
var current = 0;
|
||||
var pages = Math.ceil(num / limit);
|
||||
|
||||
// Create components
|
||||
|
||||
// Previous button
|
||||
var $left = $('<button/>', {
|
||||
html: '<',
|
||||
'class': 'button',
|
||||
title: l10n.previousPage
|
||||
}).click(function () {
|
||||
goTo(current - 1);
|
||||
});
|
||||
|
||||
// Current page text
|
||||
var $text = $('<span/>').click(function () {
|
||||
$input.width($text.width()).show().val(current + 1).focus();
|
||||
$text.hide();
|
||||
});
|
||||
|
||||
// Jump to page input
|
||||
var $input = $('<input/>', {
|
||||
type: 'number',
|
||||
min : 1,
|
||||
max: pages,
|
||||
on: {
|
||||
'blur': function () {
|
||||
gotInput();
|
||||
},
|
||||
'keyup': function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
gotInput();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).hide();
|
||||
|
||||
// Next button
|
||||
var $right = $('<button/>', {
|
||||
html: '>',
|
||||
'class': 'button',
|
||||
title: l10n.nextPage
|
||||
}).click(function () {
|
||||
goTo(current + 1);
|
||||
});
|
||||
|
||||
/**
|
||||
* Check what page the user has typed in and jump to it.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var gotInput = function () {
|
||||
var page = parseInt($input.hide().val());
|
||||
if (!isNaN(page)) {
|
||||
goTo(page - 1);
|
||||
}
|
||||
$text.show();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update UI elements.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var updateUI = function () {
|
||||
var next = current + 1;
|
||||
|
||||
// Disable or enable buttons
|
||||
$left.attr('disabled', current === 0);
|
||||
$right.attr('disabled', next === pages);
|
||||
|
||||
// Update counter
|
||||
$text.html(l10n.currentPage.replace('$current', next).replace('$total', pages));
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to go to the requested page.
|
||||
*
|
||||
* @private
|
||||
* @param {Number} page
|
||||
*/
|
||||
var goTo = function (page) {
|
||||
if (page === current || page < 0 || page >= pages) {
|
||||
return; // Invalid page number
|
||||
}
|
||||
current = page;
|
||||
|
||||
updateUI();
|
||||
|
||||
// Fire callback
|
||||
goneTo(page * limit);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update number of items and limit.
|
||||
*
|
||||
* @public
|
||||
* @param {Number} newNum Total number of items to pagiate.
|
||||
* @param {Number} newLimit Number of items to dispaly per page.
|
||||
*/
|
||||
this.update = function (newNum, newLimit) {
|
||||
if (newNum !== num || newLimit !== limit) {
|
||||
// Update num and limit
|
||||
num = newNum;
|
||||
limit = newLimit;
|
||||
pages = Math.ceil(num / limit);
|
||||
$input.attr('max', pages);
|
||||
|
||||
if (current >= pages) {
|
||||
// Content is gone, move to last page.
|
||||
goTo(pages - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
updateUI();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Append the pagination widget to the given container.
|
||||
*
|
||||
* @public
|
||||
* @param {jQuery} $container
|
||||
*/
|
||||
this.appendTo = function ($container) {
|
||||
$left.add($text).add($input).add($right).appendTo($container);
|
||||
};
|
||||
|
||||
// Update UI
|
||||
updateUI();
|
||||
};
|
||||
|
||||
})(H5P.jQuery);
|
|
@ -0,0 +1,40 @@
|
|||
H5P.Version = (function () {
|
||||
/**
|
||||
* Make it easy to keep track of version details.
|
||||
*
|
||||
* @class
|
||||
* @namespace H5P
|
||||
* @param {String} version
|
||||
*/
|
||||
function Version(version) {
|
||||
|
||||
if (typeof version === 'string') {
|
||||
// Name version string (used by content upgrade)
|
||||
var versionSplit = version.split('.', 3);
|
||||
this.major =+ versionSplit[0];
|
||||
this.minor =+ versionSplit[1];
|
||||
}
|
||||
else {
|
||||
// Library objects (used by editor)
|
||||
if (version.localMajorVersion !== undefined) {
|
||||
this.major =+ version.localMajorVersion;
|
||||
this.minor =+ version.localMinorVersion;
|
||||
}
|
||||
else {
|
||||
this.major =+ version.majorVersion;
|
||||
this.minor =+ version.minorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public. Custom string for this object.
|
||||
*
|
||||
* @returns {String}
|
||||
*/
|
||||
this.toString = function () {
|
||||
return version;
|
||||
};
|
||||
}
|
||||
|
||||
return Version;
|
||||
})();
|
|
@ -0,0 +1,331 @@
|
|||
var H5P = window.H5P = window.H5P || {};
|
||||
|
||||
/**
|
||||
* Used for xAPI events.
|
||||
*
|
||||
* @class
|
||||
* @extends H5P.Event
|
||||
*/
|
||||
H5P.XAPIEvent = function () {
|
||||
H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true});
|
||||
};
|
||||
|
||||
H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype);
|
||||
H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent;
|
||||
|
||||
/**
|
||||
* Set scored result statements.
|
||||
*
|
||||
* @param {number} score
|
||||
* @param {number} maxScore
|
||||
* @param {object} instance
|
||||
* @param {boolean} completion
|
||||
* @param {boolean} success
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setScoredResult = function (score, maxScore, instance, completion, success) {
|
||||
this.data.statement.result = {};
|
||||
|
||||
if (typeof score !== 'undefined') {
|
||||
if (typeof maxScore === 'undefined') {
|
||||
this.data.statement.result.score = {'raw': score};
|
||||
}
|
||||
else {
|
||||
this.data.statement.result.score = {
|
||||
'min': 0,
|
||||
'max': maxScore,
|
||||
'raw': score
|
||||
};
|
||||
if (maxScore > 0) {
|
||||
this.data.statement.result.score.scaled = Math.round(score / maxScore * 10000) / 10000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof completion === 'undefined') {
|
||||
this.data.statement.result.completion = (this.getVerb() === 'completed' || this.getVerb() === 'answered');
|
||||
}
|
||||
else {
|
||||
this.data.statement.result.completion = completion;
|
||||
}
|
||||
|
||||
if (typeof success !== 'undefined') {
|
||||
this.data.statement.result.success = success;
|
||||
}
|
||||
|
||||
if (instance && instance.activityStartTime) {
|
||||
var duration = Math.round((Date.now() - instance.activityStartTime ) / 10) / 100;
|
||||
// xAPI spec allows a precision of 0.01 seconds
|
||||
|
||||
this.data.statement.result.duration = 'PT' + duration + 'S';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a verb.
|
||||
*
|
||||
* @param {string} verb
|
||||
* Verb in short form, one of the verbs defined at
|
||||
* {@link http://adlnet.gov/expapi/verbs/|ADL xAPI Vocabulary}
|
||||
*
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setVerb = function (verb) {
|
||||
if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) {
|
||||
this.data.statement.verb = {
|
||||
'id': 'http://adlnet.gov/expapi/verbs/' + verb,
|
||||
'display': {
|
||||
'en-US': verb
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (verb.id !== undefined) {
|
||||
this.data.statement.verb = verb;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the statements verb id.
|
||||
*
|
||||
* @param {boolean} full
|
||||
* if true the full verb id prefixed by http://adlnet.gov/expapi/verbs/
|
||||
* will be returned
|
||||
* @returns {string}
|
||||
* Verb or null if no verb with an id has been defined
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getVerb = function (full) {
|
||||
var statement = this.data.statement;
|
||||
if ('verb' in statement) {
|
||||
if (full === true) {
|
||||
return statement.verb;
|
||||
}
|
||||
return statement.verb.id.slice(31);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the object part of the statement.
|
||||
*
|
||||
* The id is found automatically (the url to the content)
|
||||
*
|
||||
* @param {Object} instance
|
||||
* The H5P instance
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setObject = function (instance) {
|
||||
if (instance.contentId) {
|
||||
this.data.statement.object = {
|
||||
'id': this.getContentXAPIId(instance),
|
||||
'objectType': 'Activity',
|
||||
'definition': {
|
||||
'extensions': {
|
||||
'http://h5p.org/x-api/h5p-local-content-id': instance.contentId
|
||||
}
|
||||
}
|
||||
};
|
||||
if (instance.subContentId) {
|
||||
this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-subContentId'] = instance.subContentId;
|
||||
// Don't set titles on main content, title should come from publishing platform
|
||||
if (typeof instance.getTitle === 'function') {
|
||||
this.data.statement.object.definition.name = {
|
||||
"en-US": instance.getTitle()
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
var content = H5P.getContentForInstance(instance.contentId);
|
||||
if (content && content.metadata && content.metadata.title) {
|
||||
this.data.statement.object.definition.name = {
|
||||
"en-US": H5P.createTitle(content.metadata.title)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Content types view always expect to have a contentId when they are displayed.
|
||||
// This is not the case if they are displayed in the editor as part of a preview.
|
||||
// The fix is to set an empty object with definition for the xAPI event, so all
|
||||
// the content types that rely on this does not have to handle it. This means
|
||||
// that content types that are being previewed will send xAPI completed events,
|
||||
// but since there are no scripts that catch these events in the editor,
|
||||
// this is not a problem.
|
||||
this.data.statement.object = {
|
||||
definition: {}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the context part of the statement.
|
||||
*
|
||||
* @param {Object} instance
|
||||
* The H5P instance
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setContext = function (instance) {
|
||||
if (instance.parent && (instance.parent.contentId || instance.parent.subContentId)) {
|
||||
this.data.statement.context = {
|
||||
"contextActivities": {
|
||||
"parent": [
|
||||
{
|
||||
"id": this.getContentXAPIId(instance.parent),
|
||||
"objectType": "Activity"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
if (instance.libraryInfo) {
|
||||
if (this.data.statement.context === undefined) {
|
||||
this.data.statement.context = {"contextActivities":{}};
|
||||
}
|
||||
this.data.statement.context.contextActivities.category = [
|
||||
{
|
||||
"id": "http://h5p.org/libraries/" + instance.libraryInfo.versionedNameNoSpaces,
|
||||
"objectType": "Activity"
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the actor. Email and name will be added automatically.
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setActor = function () {
|
||||
if (H5PIntegration.user !== undefined) {
|
||||
this.data.statement.actor = {
|
||||
'name': H5PIntegration.user.name,
|
||||
'mbox': 'mailto:' + H5PIntegration.user.mail,
|
||||
'objectType': 'Agent'
|
||||
};
|
||||
}
|
||||
else {
|
||||
var uuid;
|
||||
try {
|
||||
if (localStorage.H5PUserUUID) {
|
||||
uuid = localStorage.H5PUserUUID;
|
||||
}
|
||||
else {
|
||||
uuid = H5P.createUUID();
|
||||
localStorage.H5PUserUUID = uuid;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// LocalStorage and Cookies are probably disabled. Do not track the user.
|
||||
uuid = 'not-trackable-' + H5P.createUUID();
|
||||
}
|
||||
this.data.statement.actor = {
|
||||
'account': {
|
||||
'name': uuid,
|
||||
'homePage': H5PIntegration.siteUrl
|
||||
},
|
||||
'objectType': 'Agent'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the max value of the result - score part of the statement
|
||||
*
|
||||
* @returns {number}
|
||||
* The max score, or null if not defined
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getMaxScore = function () {
|
||||
return this.getVerifiedStatementValue(['result', 'score', 'max']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the raw value of the result - score part of the statement
|
||||
*
|
||||
* @returns {number}
|
||||
* The score, or null if not defined
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getScore = function () {
|
||||
return this.getVerifiedStatementValue(['result', 'score', 'raw']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get content xAPI ID.
|
||||
*
|
||||
* @param {Object} instance
|
||||
* The H5P instance
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) {
|
||||
var xAPIId;
|
||||
if (instance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId]) {
|
||||
xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url;
|
||||
if (instance.subContentId) {
|
||||
xAPIId += '?subContentId=' + instance.subContentId;
|
||||
}
|
||||
}
|
||||
return xAPIId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this event is sent from a child (i.e not from grandchild)
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.isFromChild = function () {
|
||||
var parentId = this.getVerifiedStatementValue(['context', 'contextActivities', 'parent', 0, 'id']);
|
||||
return !parentId || parentId.indexOf('subContentId') === -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Figure out if a property exists in the statement and return it
|
||||
*
|
||||
* @param {string[]} keys
|
||||
* List describing the property we're looking for. For instance
|
||||
* ['result', 'score', 'raw'] for result.score.raw
|
||||
* @returns {*}
|
||||
* The value of the property if it is set, null otherwise.
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getVerifiedStatementValue = function (keys) {
|
||||
var val = this.data.statement;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (val[keys[i]] === undefined) {
|
||||
return null;
|
||||
}
|
||||
val = val[keys[i]];
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
/**
|
||||
* List of verbs defined at {@link http://adlnet.gov/expapi/verbs/|ADL xAPI Vocabulary}
|
||||
*
|
||||
* @type Array
|
||||
*/
|
||||
H5P.XAPIEvent.allowedXAPIVerbs = [
|
||||
'answered',
|
||||
'asked',
|
||||
'attempted',
|
||||
'attended',
|
||||
'commented',
|
||||
'completed',
|
||||
'exited',
|
||||
'experienced',
|
||||
'failed',
|
||||
'imported',
|
||||
'initialized',
|
||||
'interacted',
|
||||
'launched',
|
||||
'mastered',
|
||||
'passed',
|
||||
'preferred',
|
||||
'progressed',
|
||||
'registered',
|
||||
'responded',
|
||||
'resumed',
|
||||
'scored',
|
||||
'shared',
|
||||
'suspended',
|
||||
'terminated',
|
||||
'voided',
|
||||
|
||||
// Custom verbs used for action toolbar below content
|
||||
'downloaded',
|
||||
'copied',
|
||||
'accessed-reuse',
|
||||
'accessed-embed',
|
||||
'accessed-copyright'
|
||||
];
|
|
@ -0,0 +1,119 @@
|
|||
var H5P = window.H5P = window.H5P || {};
|
||||
|
||||
/**
|
||||
* The external event dispatcher. Others, outside of H5P may register and
|
||||
* listen for H5P Events here.
|
||||
*
|
||||
* @type {H5P.EventDispatcher}
|
||||
*/
|
||||
H5P.externalDispatcher = new H5P.EventDispatcher();
|
||||
|
||||
// EventDispatcher extensions
|
||||
|
||||
/**
|
||||
* Helper function for triggering xAPI added to the EventDispatcher.
|
||||
*
|
||||
* @param {string} verb
|
||||
* The short id of the verb we want to trigger
|
||||
* @param {Oject} [extra]
|
||||
* Extra properties for the xAPI statement
|
||||
*/
|
||||
H5P.EventDispatcher.prototype.triggerXAPI = function (verb, extra) {
|
||||
this.trigger(this.createXAPIEventTemplate(verb, extra));
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to create event templates added to the EventDispatcher.
|
||||
*
|
||||
* Will in the future be used to add representations of the questions to the
|
||||
* statements.
|
||||
*
|
||||
* @param {string} verb
|
||||
* Verb id in short form
|
||||
* @param {Object} [extra]
|
||||
* Extra values to be added to the statement
|
||||
* @returns {H5P.XAPIEvent}
|
||||
* Instance
|
||||
*/
|
||||
H5P.EventDispatcher.prototype.createXAPIEventTemplate = function (verb, extra) {
|
||||
var event = new H5P.XAPIEvent();
|
||||
|
||||
event.setActor();
|
||||
event.setVerb(verb);
|
||||
if (extra !== undefined) {
|
||||
for (var i in extra) {
|
||||
event.data.statement[i] = extra[i];
|
||||
}
|
||||
}
|
||||
if (!('object' in event.data.statement)) {
|
||||
event.setObject(this);
|
||||
}
|
||||
if (!('context' in event.data.statement)) {
|
||||
event.setContext(this);
|
||||
}
|
||||
return event;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to create xAPI completed events
|
||||
*
|
||||
* DEPRECATED - USE triggerXAPIScored instead
|
||||
*
|
||||
* @deprecated
|
||||
* since 1.5, use triggerXAPIScored instead.
|
||||
* @param {number} score
|
||||
* Will be set as the 'raw' value of the score object
|
||||
* @param {number} maxScore
|
||||
* will be set as the "max" value of the score object
|
||||
* @param {boolean} success
|
||||
* will be set as the "success" value of the result object
|
||||
*/
|
||||
H5P.EventDispatcher.prototype.triggerXAPICompleted = function (score, maxScore, success) {
|
||||
this.triggerXAPIScored(score, maxScore, 'completed', true, success);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to create scored xAPI events
|
||||
*
|
||||
* @param {number} score
|
||||
* Will be set as the 'raw' value of the score object
|
||||
* @param {number} maxScore
|
||||
* Will be set as the "max" value of the score object
|
||||
* @param {string} verb
|
||||
* Short form of adl verb
|
||||
* @param {boolean} completion
|
||||
* Is this a statement from a completed activity?
|
||||
* @param {boolean} success
|
||||
* Is this a statement from an activity that was done successfully?
|
||||
*/
|
||||
H5P.EventDispatcher.prototype.triggerXAPIScored = function (score, maxScore, verb, completion, success) {
|
||||
var event = this.createXAPIEventTemplate(verb);
|
||||
event.setScoredResult(score, maxScore, this, completion, success);
|
||||
this.trigger(event);
|
||||
};
|
||||
|
||||
H5P.EventDispatcher.prototype.setActivityStarted = function () {
|
||||
if (this.activityStartTime === undefined) {
|
||||
// Don't trigger xAPI events in the editor
|
||||
if (this.contentId !== undefined &&
|
||||
H5PIntegration.contents !== undefined &&
|
||||
H5PIntegration.contents['cid-' + this.contentId] !== undefined) {
|
||||
this.triggerXAPI('attempted');
|
||||
}
|
||||
this.activityStartTime = Date.now();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal H5P function listening for xAPI completed events and stores scores
|
||||
*
|
||||
* @param {H5P.XAPIEvent} event
|
||||
*/
|
||||
H5P.xAPICompletedListener = function (event) {
|
||||
if ((event.getVerb() === 'completed' || event.getVerb() === 'answered') && !event.getVerifiedStatementValue(['context', 'contextActivities', 'parent'])) {
|
||||
var score = event.getScore();
|
||||
var maxScore = event.getMaxScore();
|
||||
var contentId = event.getVerifiedStatementValue(['object', 'definition', 'extensions', 'http://h5p.org/x-api/h5p-local-content-id']);
|
||||
H5P.setFinished(contentId, score, maxScore);
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,436 @@
|
|||
/**
|
||||
* Queue requests and handle them at your convenience
|
||||
*
|
||||
* @type {RequestQueue}
|
||||
*/
|
||||
H5P.RequestQueue = (function ($, EventDispatcher) {
|
||||
/**
|
||||
* A queue for requests, will be automatically processed when regaining connection
|
||||
*
|
||||
* @param {boolean} [options.showToast] Show toast when losing or regaining connection
|
||||
* @constructor
|
||||
*/
|
||||
const RequestQueue = function (options) {
|
||||
EventDispatcher.call(this);
|
||||
this.processingQueue = false;
|
||||
options = options || {};
|
||||
|
||||
this.showToast = options.showToast;
|
||||
this.itemName = 'requestQueue';
|
||||
};
|
||||
|
||||
/**
|
||||
* Add request to queue. Only supports posts currently.
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {Object} data
|
||||
* @returns {boolean}
|
||||
*/
|
||||
RequestQueue.prototype.add = function (url, data) {
|
||||
if (!window.localStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let storedStatements = this.getStoredRequests();
|
||||
if (!storedStatements) {
|
||||
storedStatements = [];
|
||||
}
|
||||
|
||||
storedStatements.push({
|
||||
url: url,
|
||||
data: data,
|
||||
});
|
||||
|
||||
window.localStorage.setItem(this.itemName, JSON.stringify(storedStatements));
|
||||
|
||||
this.trigger('requestQueued', {
|
||||
storedStatements: storedStatements,
|
||||
processingQueue: this.processingQueue,
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get stored requests
|
||||
*
|
||||
* @returns {boolean|Array} Stored requests
|
||||
*/
|
||||
RequestQueue.prototype.getStoredRequests = function () {
|
||||
if (!window.localStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const item = window.localStorage.getItem(this.itemName);
|
||||
if (!item) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return JSON.parse(item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear stored requests
|
||||
*
|
||||
* @returns {boolean} True if the storage was successfully cleared
|
||||
*/
|
||||
RequestQueue.prototype.clearQueue = function () {
|
||||
if (!window.localStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
window.localStorage.removeItem(this.itemName);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start processing of requests queue
|
||||
*
|
||||
* @return {boolean} Returns false if it was not possible to resume processing queue
|
||||
*/
|
||||
RequestQueue.prototype.resumeQueue = function () {
|
||||
// Not supported
|
||||
if (!H5PIntegration || !window.navigator || !window.localStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Already processing
|
||||
if (this.processingQueue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to send queued requests
|
||||
const queue = this.getStoredRequests();
|
||||
const queueLength = queue.length;
|
||||
|
||||
// Clear storage, failed requests will be re-added
|
||||
this.clearQueue();
|
||||
|
||||
// No items left in queue
|
||||
if (!queueLength) {
|
||||
this.trigger('emptiedQueue', queue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make sure requests are not changed while they're being handled
|
||||
this.processingQueue = true;
|
||||
|
||||
// Process queue in original order
|
||||
this.processQueue(queue);
|
||||
return true
|
||||
};
|
||||
|
||||
/**
|
||||
* Process first item in the request queue
|
||||
*
|
||||
* @param {Array} queue Request queue
|
||||
*/
|
||||
RequestQueue.prototype.processQueue = function (queue) {
|
||||
if (!queue.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.trigger('processingQueue');
|
||||
|
||||
// Make sure the requests are processed in a FIFO order
|
||||
const request = queue.shift();
|
||||
|
||||
const self = this;
|
||||
$.post(request.url, request.data)
|
||||
.fail(self.onQueuedRequestFail.bind(self, request))
|
||||
.always(self.onQueuedRequestProcessed.bind(self, queue))
|
||||
};
|
||||
|
||||
/**
|
||||
* Request fail handler
|
||||
*
|
||||
* @param {Object} request
|
||||
*/
|
||||
RequestQueue.prototype.onQueuedRequestFail = function (request) {
|
||||
// Queue the failed request again if we're offline
|
||||
if (!window.navigator.onLine) {
|
||||
this.add(request.url, request.data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An item in the queue was processed
|
||||
*
|
||||
* @param {Array} queue Queue that was processed
|
||||
*/
|
||||
RequestQueue.prototype.onQueuedRequestProcessed = function (queue) {
|
||||
if (queue.length) {
|
||||
this.processQueue(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Finished processing this queue
|
||||
this.processingQueue = false;
|
||||
|
||||
// Run empty queue callback with next request queue
|
||||
const requestQueue = this.getStoredRequests();
|
||||
this.trigger('queueEmptied', requestQueue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Display toast message on the first content of current page
|
||||
*
|
||||
* @param {string} msg Message to display
|
||||
* @param {boolean} [forceShow] Force override showing the toast
|
||||
* @param {Object} [configOverride] Override toast message config
|
||||
*/
|
||||
RequestQueue.prototype.displayToastMessage = function (msg, forceShow, configOverride) {
|
||||
if (!this.showToast && !forceShow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = H5P.jQuery.extend(true, {}, {
|
||||
position: {
|
||||
horizontal : 'centered',
|
||||
vertical: 'centered',
|
||||
noOverflowX: true,
|
||||
}
|
||||
}, configOverride);
|
||||
|
||||
H5P.attachToastTo(H5P.jQuery('.h5p-content:first')[0], msg, config);
|
||||
};
|
||||
|
||||
return RequestQueue;
|
||||
})(H5P.jQuery, H5P.EventDispatcher);
|
||||
|
||||
/**
|
||||
* Request queue for retrying failing requests, will automatically retry them when you come online
|
||||
*
|
||||
* @type {offlineRequestQueue}
|
||||
*/
|
||||
H5P.OfflineRequestQueue = (function (RequestQueue, Dialog) {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param {Object} [options] Options for offline request queue
|
||||
* @param {Object} [options.instance] The H5P instance which UI components are placed within
|
||||
*/
|
||||
const offlineRequestQueue = function (options) {
|
||||
const requestQueue = new RequestQueue();
|
||||
|
||||
// We could handle requests from previous pages here, but instead we throw them away
|
||||
requestQueue.clearQueue();
|
||||
|
||||
let startTime = null;
|
||||
const retryIntervals = [10, 20, 40, 60, 120, 300, 600];
|
||||
let intervalIndex = -1;
|
||||
let currentInterval = null;
|
||||
let isAttached = false;
|
||||
let isShowing = false;
|
||||
let isLoading = false;
|
||||
const instance = options.instance;
|
||||
|
||||
const offlineDialog = new Dialog({
|
||||
headerText: H5P.t('offlineDialogHeader'),
|
||||
dialogText: H5P.t('offlineDialogBody'),
|
||||
confirmText: H5P.t('offlineDialogRetryButtonLabel'),
|
||||
hideCancel: true,
|
||||
hideExit: true,
|
||||
classes: ['offline'],
|
||||
instance: instance,
|
||||
skipRestoreFocus: true,
|
||||
});
|
||||
|
||||
const dialog = offlineDialog.getElement();
|
||||
|
||||
// Add retry text to body
|
||||
const countDownText = document.createElement('div');
|
||||
countDownText.classList.add('count-down');
|
||||
countDownText.innerHTML = H5P.t('offlineDialogRetryMessage')
|
||||
.replace(':num', '<span class="count-down-num">0</span>');
|
||||
|
||||
dialog.querySelector('.h5p-confirmation-dialog-text').appendChild(countDownText);
|
||||
const countDownNum = countDownText.querySelector('.count-down-num');
|
||||
|
||||
// Create throbber
|
||||
const throbberWrapper = document.createElement('div');
|
||||
throbberWrapper.classList.add('throbber-wrapper');
|
||||
const throbber = document.createElement('div');
|
||||
throbber.classList.add('sending-requests-throbber');
|
||||
throbberWrapper.appendChild(throbber);
|
||||
|
||||
requestQueue.on('requestQueued', function (e) {
|
||||
// Already processing queue, wait until queue has finished processing before showing dialog
|
||||
if (e.data && e.data.processingQueue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAttached) {
|
||||
const rootContent = document.body.querySelector('.h5p-content');
|
||||
if (!rootContent) {
|
||||
return;
|
||||
}
|
||||
offlineDialog.appendTo(rootContent);
|
||||
rootContent.appendChild(throbberWrapper);
|
||||
isAttached = true;
|
||||
}
|
||||
|
||||
startCountDown();
|
||||
}.bind(this));
|
||||
|
||||
requestQueue.on('queueEmptied', function (e) {
|
||||
if (e.data && e.data.length) {
|
||||
// New requests were added while processing queue or requests failed again. Re-queue requests.
|
||||
startCountDown(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Successfully emptied queue
|
||||
clearInterval(currentInterval);
|
||||
toggleThrobber(false);
|
||||
intervalIndex = -1;
|
||||
if (isShowing) {
|
||||
offlineDialog.hide();
|
||||
isShowing = false;
|
||||
}
|
||||
|
||||
requestQueue.displayToastMessage(
|
||||
H5P.t('offlineSuccessfulSubmit'),
|
||||
true,
|
||||
{
|
||||
position: {
|
||||
vertical: 'top',
|
||||
offsetVertical: '100',
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}.bind(this));
|
||||
|
||||
offlineDialog.on('confirmed', function () {
|
||||
// Show dialog on next render in case it is being hidden by the 'confirm' button
|
||||
isShowing = false;
|
||||
setTimeout(function () {
|
||||
retryRequests();
|
||||
}, 100);
|
||||
}.bind(this));
|
||||
|
||||
// Initialize listener for when requests are added to queue
|
||||
window.addEventListener('online', function () {
|
||||
retryRequests();
|
||||
}.bind(this));
|
||||
|
||||
// Listen for queued requests outside the iframe
|
||||
window.addEventListener('message', function (event) {
|
||||
const isValidQueueEvent = window.parent === event.source
|
||||
&& event.data.context === 'h5p'
|
||||
&& event.data.action === 'queueRequest';
|
||||
|
||||
if (!isValidQueueEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.add(event.data.url, event.data.data);
|
||||
}.bind(this));
|
||||
|
||||
/**
|
||||
* Toggle throbber visibility
|
||||
*
|
||||
* @param {boolean} [forceShow] Will force throbber visibility if set
|
||||
*/
|
||||
const toggleThrobber = function (forceShow) {
|
||||
isLoading = !isLoading;
|
||||
if (forceShow !== undefined) {
|
||||
isLoading = forceShow;
|
||||
}
|
||||
|
||||
if (isLoading && isShowing) {
|
||||
offlineDialog.hide();
|
||||
isShowing = false;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
throbberWrapper.classList.add('show');
|
||||
}
|
||||
else {
|
||||
throbberWrapper.classList.remove('show');
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Retries the failed requests
|
||||
*/
|
||||
const retryRequests = function () {
|
||||
clearInterval(currentInterval);
|
||||
toggleThrobber(true);
|
||||
requestQueue.resumeQueue();
|
||||
};
|
||||
|
||||
/**
|
||||
* Increments retry interval
|
||||
*/
|
||||
const incrementRetryInterval = function () {
|
||||
intervalIndex += 1;
|
||||
if (intervalIndex >= retryIntervals.length) {
|
||||
intervalIndex = retryIntervals.length - 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts counting down to retrying queued requests.
|
||||
*
|
||||
* @param forceDelayedShow
|
||||
*/
|
||||
const startCountDown = function (forceDelayedShow) {
|
||||
// Already showing, wait for retry
|
||||
if (isShowing) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleThrobber(false);
|
||||
if (!isShowing) {
|
||||
if (forceDelayedShow) {
|
||||
// Must force delayed show since dialog may be hiding, and confirmation dialog does not
|
||||
// support this.
|
||||
setTimeout(function () {
|
||||
offlineDialog.show(0);
|
||||
}, 100);
|
||||
}
|
||||
else {
|
||||
offlineDialog.show(0);
|
||||
}
|
||||
}
|
||||
isShowing = true;
|
||||
startTime = new Date().getTime();
|
||||
incrementRetryInterval();
|
||||
clearInterval(currentInterval);
|
||||
currentInterval = setInterval(updateCountDown, 100);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the count down timer. Retries requests when time expires.
|
||||
*/
|
||||
const updateCountDown = function () {
|
||||
const time = new Date().getTime();
|
||||
const timeElapsed = Math.floor((time - startTime) / 1000);
|
||||
const timeLeft = retryIntervals[intervalIndex] - timeElapsed;
|
||||
countDownNum.textContent = timeLeft.toString();
|
||||
|
||||
// Retry interval reached, retry requests
|
||||
if (timeLeft <= 0) {
|
||||
retryRequests();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add request to offline request queue. Only supports posts for now.
|
||||
*
|
||||
* @param {string} url The request url
|
||||
* @param {Object} data The request data
|
||||
*/
|
||||
this.add = function (url, data) {
|
||||
// Only queue request if it failed because we are offline
|
||||
if (window.navigator.onLine) {
|
||||
return false;
|
||||
}
|
||||
|
||||
requestQueue.add(url, data);
|
||||
};
|
||||
};
|
||||
|
||||
return offlineRequestQueue;
|
||||
})(H5P.RequestQueue, H5P.ConfirmationDialog);
|
|
@ -0,0 +1,68 @@
|
|||
/* global H5PDisableHubData */
|
||||
|
||||
/**
|
||||
* Global data for disable hub functionality
|
||||
*
|
||||
* @typedef {object} H5PDisableHubData Data passed in from the backend
|
||||
*
|
||||
* @property {string} selector Selector for the disable hub check-button
|
||||
* @property {string} overlaySelector Selector for the element that the confirmation dialog will mask
|
||||
* @property {Array} errors Errors found with the current server setup
|
||||
*
|
||||
* @property {string} header Header of the confirmation dialog
|
||||
* @property {string} confirmationDialogMsg Body of the confirmation dialog
|
||||
* @property {string} cancelLabel Cancel label of the confirmation dialog
|
||||
* @property {string} confirmLabel Confirm button label of the confirmation dialog
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* Utility that makes it possible to force the user to confirm that he really
|
||||
* wants to use the H5P hub without proper server settings.
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
$(document).on('ready', function () {
|
||||
|
||||
// No data found
|
||||
if (!H5PDisableHubData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No errors found, no need for confirmation dialog
|
||||
if (!H5PDisableHubData.errors || !H5PDisableHubData.errors.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
H5PDisableHubData.selector = H5PDisableHubData.selector ||
|
||||
'.h5p-settings-disable-hub-checkbox';
|
||||
H5PDisableHubData.overlaySelector = H5PDisableHubData.overlaySelector ||
|
||||
'.h5p-settings-container';
|
||||
|
||||
var dialogHtml = '<div>' +
|
||||
'<p>' + H5PDisableHubData.errors.join('</p><p>') + '</p>' +
|
||||
'<p>' + H5PDisableHubData.confirmationDialogMsg + '</p>';
|
||||
|
||||
// Create confirmation dialog, make sure to include translations
|
||||
var confirmationDialog = new H5P.ConfirmationDialog({
|
||||
headerText: H5PDisableHubData.header,
|
||||
dialogText: dialogHtml,
|
||||
cancelText: H5PDisableHubData.cancelLabel,
|
||||
confirmText: H5PDisableHubData.confirmLabel
|
||||
}).appendTo($(H5PDisableHubData.overlaySelector).get(0));
|
||||
|
||||
confirmationDialog.on('confirmed', function () {
|
||||
enableButton.get(0).checked = true;
|
||||
});
|
||||
|
||||
confirmationDialog.on('canceled', function () {
|
||||
enableButton.get(0).checked = false;
|
||||
});
|
||||
|
||||
var enableButton = $(H5PDisableHubData.selector);
|
||||
enableButton.change(function () {
|
||||
if ($(this).is(':checked')) {
|
||||
confirmationDialog.show(enableButton.offset().top);
|
||||
}
|
||||
});
|
||||
});
|
||||
})(H5P.jQuery);
|
|
@ -0,0 +1,358 @@
|
|||
/* Administration interface styling */
|
||||
|
||||
.h5p-content {
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.h5p-admin-table,
|
||||
.h5p-admin-table > tbody {
|
||||
border: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h5p-admin-table tr:nth-child(odd),
|
||||
.h5p-data-view tr:nth-child(odd) {
|
||||
background-color: #F9F9F9;
|
||||
}
|
||||
.h5p-admin-table tbody tr:hover {
|
||||
background-color: #EEE;
|
||||
}
|
||||
.h5p-admin-table.empty {
|
||||
padding: 1em;
|
||||
background-color: #EEE;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.h5p-admin-table.libraries th:last-child,
|
||||
.h5p-admin-table.libraries td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.h5p-admin-buttons-wrapper {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.h5p-admin-table.libraries button {
|
||||
font-size: 2em;
|
||||
cursor: pointer;
|
||||
border: 1px solid #AAA;
|
||||
border-radius: .2em;
|
||||
background-color: #e0e0e0;
|
||||
text-shadow: 0 0 0.5em #fff;
|
||||
padding: 0;
|
||||
line-height: 1em;
|
||||
width: 1.125em;
|
||||
height: 1.05em;
|
||||
text-indent: -0.125em;
|
||||
margin: 0.125em 0.125em 0 0.125em;
|
||||
}
|
||||
.h5p-admin-upgrade-library:before {
|
||||
font-family: 'H5P';
|
||||
content: "\e888";
|
||||
}
|
||||
.h5p-admin-view-library:before {
|
||||
font-family: 'H5P';
|
||||
content: "\e889";
|
||||
}
|
||||
.h5p-admin-delete-library:before {
|
||||
font-family: 'H5P';
|
||||
content: "\e890";
|
||||
}
|
||||
|
||||
.h5p-admin-table.libraries button:hover {
|
||||
background-color: #d0d0d0;
|
||||
}
|
||||
.h5p-admin-table.libraries button:disabled:hover {
|
||||
background-color: #e0e0e0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.h5p-admin-upgrade-library {
|
||||
color: #339900;
|
||||
}
|
||||
.h5p-admin-view-library {
|
||||
color: #0066cc;
|
||||
}
|
||||
.h5p-admin-delete-library {
|
||||
color: #990000;
|
||||
}
|
||||
.h5p-admin-delete-library:disabled,
|
||||
.h5p-admin-upgrade-library:disabled {
|
||||
cursor: default;
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
||||
.h5p-library-info {
|
||||
padding: 1em 1em;
|
||||
margin: 1em 0;
|
||||
|
||||
width: 350px;
|
||||
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Labeled field (label + value) */
|
||||
.h5p-labeled-field {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
.h5p-labeled-field:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.h5p-labeled-field .h5p-label {
|
||||
display: inline-block;
|
||||
min-width: 150px;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
.h5p-labeled-field .h5p-value {
|
||||
display: inline-block;
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
/* Search element */
|
||||
.h5p-content-search {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
margin-top: 10px;
|
||||
|
||||
border: 1px solid #CCC;
|
||||
border-radius: 3px;
|
||||
box-shadow: 2px 2px 5px #888888;
|
||||
}
|
||||
.h5p-content-search:before {
|
||||
font-family: 'H5P';
|
||||
vertical-align: bottom;
|
||||
content: "\e88a";
|
||||
font-size: 2em;
|
||||
line-height: 1.25em;
|
||||
}
|
||||
.h5p-content-search input {
|
||||
font-size: 120%;
|
||||
line-height: 120%;
|
||||
}
|
||||
.h5p-admin-search-results {
|
||||
margin-left: 10px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.h5p-admin-pager-size-selector {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: .75em;
|
||||
display: inline-block;
|
||||
}
|
||||
.h5p-admin-pager-size-selector > span {
|
||||
padding: 5px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #CCC;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.h5p-admin-pager-size-selector > span.selected {
|
||||
background-color: #edf5fa;
|
||||
}
|
||||
.h5p-admin-pager-size-selector > span:hover {
|
||||
background-color: #555;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
/* Generic "javascript"-action button */
|
||||
button.h5p-admin {
|
||||
border: 1px solid #AAA;
|
||||
border-radius: 5px;
|
||||
padding: 3px 10px;
|
||||
background-color: #EEE;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: #222;
|
||||
}
|
||||
button.h5p-admin:hover {
|
||||
background-color: #555;
|
||||
color: #FFF;
|
||||
}
|
||||
button.h5p-admin.disabled,
|
||||
button.h5p-admin.disabled:hover {
|
||||
cursor: auto;
|
||||
color: #CCC;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
/* Pager element */
|
||||
.h5p-content-pager {
|
||||
display: inline-block;
|
||||
border: 1px solid #CCC;
|
||||
border-radius: 3px;
|
||||
box-shadow: 2px 2px 5px #888888;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 3px 0;
|
||||
}
|
||||
.h5p-content-pager > button {
|
||||
min-width: 80px;
|
||||
font-size: 130%;
|
||||
line-height: 130%;
|
||||
border: none;
|
||||
background: none;
|
||||
font-family: 'H5P';
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.h5p-content-pager > button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
.h5p-content-pager > button:last-child {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.h5p-content-pager > .pager-info {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.h5p-content-pager > .pager-info:hover {
|
||||
background-color: #555;
|
||||
color: #FFF;
|
||||
}
|
||||
.h5p-content-pager > .pager-info,
|
||||
.h5p-content-pager > .h5p-pager-goto {
|
||||
margin: 0 10px;
|
||||
line-height: 130%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.h5p-admin-header {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
#h5p-library-upload-form.h5p-admin-upload-libraries-form,
|
||||
#h5p-content-type-cache-update-form.h5p-admin-upload-libraries-form {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
|
||||
}
|
||||
.h5p-admin-upload-libraries-form .form-submit {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.h5p-spinner {
|
||||
padding: 0 0.5em;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
#h5p-admin-container .h5p-admin-center {
|
||||
text-align: center;
|
||||
}
|
||||
.h5p-pagination {
|
||||
text-align: center;
|
||||
}
|
||||
.h5p-pagination > span, .h5p-pagination > input {
|
||||
margin: 0 1em;
|
||||
}
|
||||
.h5p-data-view input[type="text"] {
|
||||
margin-bottom: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
float: left;
|
||||
}
|
||||
.h5p-data-view input[type="text"]::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.h5p-data-view .h5p-others-contents-toggler-wrapper {
|
||||
float: right;
|
||||
line-height: 2;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.h5p-data-view .h5p-others-contents-toggler-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.h5p-data-view .h5p-others-contents-toggler {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.h5p-data-view th[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
.h5p-data-view th[role="button"].h5p-sort:after,
|
||||
.h5p-data-view th[role="button"]:hover:after,
|
||||
.h5p-data-view th[role="button"].h5p-sort.h5p-reverse:hover:after {
|
||||
content: "\25BE";
|
||||
position: relative;
|
||||
left: 0.5em;
|
||||
top: -1px;
|
||||
}
|
||||
.h5p-data-view th[role="button"].h5p-sort.h5p-reverse:after,
|
||||
.h5p-data-view th[role="button"].h5p-sort:hover:after {
|
||||
content: "\25B4";
|
||||
top: -2px;
|
||||
}
|
||||
.h5p-data-view th[role="button"]:hover:after,
|
||||
.h5p-data-view th[role="button"].h5p-sort.h5p-reverse:hover:after,
|
||||
.h5p-data-view th[role="button"].h5p-sort:hover:after {
|
||||
color: #999;
|
||||
}
|
||||
.h5p-data-view .h5p-facet {
|
||||
cursor: pointer;
|
||||
color: #0073aa;
|
||||
outline: none;
|
||||
}
|
||||
.h5p-data-view .h5p-facet:hover,
|
||||
.h5p-data-view .h5p-facet:active {
|
||||
color: #00a0d2;
|
||||
}
|
||||
.h5p-data-view .h5p-facet:focus {
|
||||
color: #124964;
|
||||
box-shadow: 0 0 0 1px #5b9dd9,0 0 2px 1px rgba(30,140,190,.8);
|
||||
}
|
||||
.h5p-data-view .h5p-facet-wrapper {
|
||||
line-height: 23px;
|
||||
}
|
||||
.h5p-data-view .h5p-facet-tag {
|
||||
margin: 2px 0 0 0.5em;
|
||||
font-size: 12px;
|
||||
background: #e8e8e8;
|
||||
border: 1px solid #cbcbcc;
|
||||
border-radius: 5px;
|
||||
color: #5d5d5d;
|
||||
padding: 0 24px 0 10px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.h5p-data-view .h5p-facet-tag > span {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
font-size: 18px;
|
||||
color: #a2a2a2;
|
||||
outline: none;
|
||||
width: 21px;
|
||||
text-indent: 4px;
|
||||
letter-spacing: 10px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
.h5p-data-view .h5p-facet-tag > span:before {
|
||||
content: "×";
|
||||
font-weight: bold;
|
||||
}
|
||||
.h5p-data-view .h5p-facet-tag > span:hover,
|
||||
.h5p-data-view .h5p-facet-tag > span:focus {
|
||||
color: #a20000;
|
||||
}
|
||||
.h5p-data-view .h5p-facet-tag > span:active {
|
||||
color: #d20000;
|
||||
}
|
||||
.content-upgrade-log {
|
||||
color: red;
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
.h5p-confirmation-dialog-background {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
background: rgba(44, 44, 44, 0.9);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
-webkit-transition: opacity 0.1s, linear 0s, visibility 0s linear 0s;
|
||||
transition: opacity 0.1s linear 0s, visibility 0s linear 0s;
|
||||
|
||||
z-index: 201;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-background.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-background.hiding {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
-webkit-transition: opacity 0.1s, linear 0s, visibility 0s linear 0.1s;
|
||||
transition: opacity 0.1s linear 0s, visibility 0s linear 0.1s;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-popup:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-popup {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
max-width: 35em;
|
||||
min-width: 25em;
|
||||
|
||||
top: 2em;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-50%, 0%);
|
||||
-ms-transform: translate(-50%, 0%);
|
||||
transform: translate(-50%, 0%);
|
||||
|
||||
color: #555;
|
||||
box-shadow: 0 0 6px 6px rgba(10,10,10,0.3);
|
||||
|
||||
-webkit-transition: transform 0.1s ease-in;
|
||||
transition: transform 0.1s ease-in;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-popup.hidden {
|
||||
-webkit-transform: translate(-50%, 50%);
|
||||
-ms-transform: translate(-50%, 50%);
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-header {
|
||||
padding: 1.5em;
|
||||
background: #fff;
|
||||
color: #356593;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-header-text {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-body {
|
||||
background: #fafbfc;
|
||||
border-top: solid 1px #dde0e9;
|
||||
padding: 1.25em 1.5em;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-text {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-buttons {
|
||||
float: right;
|
||||
}
|
||||
|
||||
button.h5p-confirmation-dialog-exit:visited,
|
||||
button.h5p-confirmation-dialog-exit:link,
|
||||
button.h5p-confirmation-dialog-exit {
|
||||
position: absolute;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 2.5em;
|
||||
top: -0.9em;
|
||||
right: -1.15em;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button.h5p-confirmation-dialog-exit:focus,
|
||||
button.h5p-confirmation-dialog-exit:hover {
|
||||
color: #E4ECF5;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-exit:before {
|
||||
font-family: "H5P";
|
||||
content: "\e890";
|
||||
}
|
||||
|
||||
.h5p-core-button.h5p-confirmation-dialog-confirm-button {
|
||||
padding-left: 0.75em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.h5p-core-button.h5p-confirmation-dialog-confirm-button:before {
|
||||
content: "\e601";
|
||||
margin-top: -6px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-popup.offline .h5p-confirmation-dialog-buttons {
|
||||
float: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-popup.offline .count-down {
|
||||
font-family: Arial;
|
||||
margin-top: 0.15em;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.h5p-confirmation-dialog-popup.offline .h5p-confirmation-dialog-confirm-button:before {
|
||||
content: "\e90b";
|
||||
font-weight: normal;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.throbber-wrapper {
|
||||
display: none;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background: rgba(44, 44, 44, 0.9);
|
||||
}
|
||||
|
||||
.throbber-wrapper.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.throbber-wrapper .throbber-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.throbber-wrapper .sending-requests-throbber{
|
||||
position: absolute;
|
||||
top: 7em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.throbber-wrapper .sending-requests-throbber:before {
|
||||
display: block;
|
||||
font-family: 'H5P';
|
||||
content: "\e90b";
|
||||
color: white;
|
||||
font-size: 10em;
|
||||
animation: request-throbber 1.5s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes request-throbber {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
button.h5p-core-button:visited,
|
||||
button.h5p-core-button:link,
|
||||
button.h5p-core-button {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 1em;
|
||||
line-height: 1.2;
|
||||
padding: 0.5em 1.25em;
|
||||
border-radius: 2em;
|
||||
|
||||
background: #2579c6;
|
||||
color: #fff;
|
||||
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
vertical-align: baseline;
|
||||
text-decoration: none;
|
||||
|
||||
-webkit-transition: initial;
|
||||
transition: initial;
|
||||
}
|
||||
button.h5p-core-button:focus {
|
||||
background: #1f67a8;
|
||||
}
|
||||
button.h5p-core-button:hover {
|
||||
background: rgba(31, 103, 168, 0.83);
|
||||
}
|
||||
button.h5p-core-button:active {
|
||||
background: #104888;
|
||||
}
|
||||
button.h5p-core-button:before {
|
||||
font-family: 'H5P';
|
||||
padding-right: 0.15em;
|
||||
font-size: 1.5em;
|
||||
vertical-align: middle;
|
||||
line-height: 0.7;
|
||||
}
|
||||
button.h5p-core-cancel-button:visited,
|
||||
button.h5p-core-cancel-button:link,
|
||||
button.h5p-core-cancel-button {
|
||||
border: none;
|
||||
background: none;
|
||||
color: #a00;
|
||||
margin-right: 1em;
|
||||
font-size: 1em;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
button.h5p-core-cancel-button:hover,
|
||||
button.h5p-core-cancel-button:focus {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #e40000;
|
||||
}
|
|
@ -0,0 +1,566 @@
|
|||
/* General CSS for H5P. Licensed under the MIT License.*/
|
||||
|
||||
/* Custom H5P font to use for icons. */
|
||||
@font-face {
|
||||
font-family: 'h5p';
|
||||
src: url('../fonts/h5p-core-23.eot?mz1lkp');
|
||||
src: url('../fonts/h5p-core-23.eot?mz1lkp#iefix') format('embedded-opentype'),
|
||||
url('../fonts/h5p-core-23.ttf?mz1lkp') format('truetype'),
|
||||
url('../fonts/h5p-core-23.woff?mz1lkp') format('woff'),
|
||||
url('../fonts/h5p-core-23.svg?mz1lkp#h5p') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
html.h5p-iframe, html.h5p-iframe > body {
|
||||
font-family: Sans-Serif; /* Use the browser's default sans-serif font. (Since Heletica doesn't look nice on Windows, and Arial on OS X.) */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.h5p-semi-fullscreen, .h5p-fullscreen, html.h5p-iframe .h5p-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.h5p-content {
|
||||
position: relative;
|
||||
background: #fefefe;
|
||||
border: 1px solid #EEE;
|
||||
border-bottom: none;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
.h5p-noselect
|
||||
{
|
||||
-khtml-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
html.h5p-iframe .h5p-content {
|
||||
font-size: 16px;
|
||||
line-height: 1.5em;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
html.h5p-iframe .h5p-fullscreen .h5p-content,
|
||||
html.h5p-iframe .h5p-semi-fullscreen .h5p-content {
|
||||
height: 100%;
|
||||
}
|
||||
.h5p-content.h5p-no-frame,
|
||||
.h5p-fullscreen .h5p-content,
|
||||
.h5p-semi-fullscreen .h5p-content {
|
||||
border: 0;
|
||||
}
|
||||
.h5p-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.h5p-iframe-wrapper.h5p-fullscreen {
|
||||
background-color: #000;
|
||||
}
|
||||
body.h5p-semi-fullscreen {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.h5p-container.h5p-semi-fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 101;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.h5p-content-controls {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
.h5p-fullscreen .h5p-content-controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.h5p-content-controls > a:link, .h5p-content-controls > a:visited, a.h5p-disable-fullscreen:link, a.h5p-disable-fullscreen:visited {
|
||||
color: #e5eef6;
|
||||
}
|
||||
|
||||
.h5p-enable-fullscreen:before {
|
||||
font-family: 'H5P';
|
||||
content: "\e88c";
|
||||
}
|
||||
.h5p-disable-fullscreen:before {
|
||||
font-family: 'H5P';
|
||||
content: "\e891";
|
||||
}
|
||||
.h5p-enable-fullscreen, .h5p-disable-fullscreen {
|
||||
cursor: pointer;
|
||||
color: #EEE;
|
||||
background: rgb(0,0,0);
|
||||
background: rgba(0,0,0,0.3);
|
||||
line-height: 0.975em;
|
||||
font-size: 2em;
|
||||
width: 1.125em;
|
||||
height: 1em;
|
||||
text-indent: 0.04em;
|
||||
}
|
||||
.h5p-disable-fullscreen {
|
||||
line-height: 0.925em;
|
||||
width: 1.1em;
|
||||
height: 0.9em;
|
||||
}
|
||||
|
||||
.h5p-enable-fullscreen:focus,
|
||||
.h5p-disable-fullscreen:focus {
|
||||
outline-style: solid;
|
||||
outline-width: 1px;
|
||||
outline-offset: 0.25em;
|
||||
}
|
||||
|
||||
.h5p-enable-fullscreen:hover, .h5p-disable-fullscreen:hover {
|
||||
background: rgba(0,0,0,0.5);
|
||||
}
|
||||
.h5p-semi-fullscreen .h5p-enable-fullscreen {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.h5p-fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.h5p-iframe-wrapper {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.h5p-fullscreen .h5p-iframe-wrapper,
|
||||
.h5p-semi-fullscreen .h5p-iframe-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h5p-iframe-wrapper.h5p-semi-fullscreen {
|
||||
width: auto;
|
||||
height: auto;
|
||||
background: black;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 100001;
|
||||
}
|
||||
.h5p-iframe-wrapper.h5p-semi-fullscreen .buttons {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
.h5p-iframe-wrapper iframe.h5p-iframe {
|
||||
/* Hack for IOS landscape / portrait */
|
||||
width: 10px;
|
||||
min-width: 100%;
|
||||
*width: 100%;
|
||||
/* End of hack */
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.h5p-content ul.h5p-actions {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
list-style: none;
|
||||
padding: 0px 10px;
|
||||
margin: 0;
|
||||
height: 25px;
|
||||
font-size: 12px;
|
||||
background: #FAFAFA;
|
||||
border-top: 1px solid #EEE;
|
||||
border-bottom: 1px solid #EEE;
|
||||
clear: both;
|
||||
font-family: Sans-Serif;
|
||||
}
|
||||
.h5p-fullscreen .h5p-actions, .h5p-semi-fullscreen .h5p-actions {
|
||||
display: none;
|
||||
}
|
||||
.h5p-actions > .h5p-button {
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
margin: 0 0.5em 0 0;
|
||||
background: none;
|
||||
padding: 0 0.75em 0 0.25em;
|
||||
vertical-align: top;
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
line-height: 23px;
|
||||
}
|
||||
.h5p-actions > .h5p-button:hover {
|
||||
color: #666;
|
||||
}
|
||||
.h5p-actions > .h5p-button:active,
|
||||
.h5p-actions > .h5p-button:focus,
|
||||
.h5p-actions .h5p-link:active,
|
||||
.h5p-actions .h5p-link:focus {
|
||||
color: #666;
|
||||
}
|
||||
.h5p-actions > .h5p-button:focus,
|
||||
.h5p-actions .h5p-link:focus {
|
||||
outline-style: solid;
|
||||
outline-width: thin;
|
||||
outline-offset: -2px;
|
||||
outline-color: #9ecaed;
|
||||
}
|
||||
.h5p-actions > .h5p-button:before {
|
||||
font-family: 'H5P';
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
vertical-align: top;
|
||||
padding-right: 0;
|
||||
}
|
||||
.h5p-actions > .h5p-button.h5p-export:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
.h5p-actions > .h5p-button.h5p-copyrights:before {
|
||||
content: "\e88f";
|
||||
}
|
||||
.h5p-actions > .h5p-button.h5p-embed:before {
|
||||
content: "\e892";
|
||||
}
|
||||
.h5p-actions .h5p-link {
|
||||
float: right;
|
||||
margin-right: 0;
|
||||
font-size: 2.0em;
|
||||
line-height: 23px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
.h5p-actions .h5p-link:before {
|
||||
font-family: 'H5P';
|
||||
content: "\e88e";
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.h5p-actions > li {
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.h5p-popup-dialog {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
z-index: 100;
|
||||
padding: 2em;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s;
|
||||
-moz-transition: opacity 0.2s;
|
||||
-o-transition: opacity 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
background:#000;
|
||||
background:rgba(0,0,0,0.75);
|
||||
}
|
||||
.h5p-popup-dialog.h5p-open {
|
||||
opacity: 1;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-inner {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-inner > h2 {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
background: #eee;
|
||||
display: block;
|
||||
color: #656565;
|
||||
font-size: 1.25em;
|
||||
padding: 0.325em 0.5em 0.25em;
|
||||
line-height: 1.25em;
|
||||
border-bottom: 1px solid #ccc;
|
||||
z-index: 2;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-inner > h2 > a {
|
||||
font-size: 12px;
|
||||
margin-left: 1em;
|
||||
}
|
||||
.h5p-embed-dialog .h5p-inner,
|
||||
.h5p-reuse-dialog .h5p-inner,
|
||||
.h5p-content-user-data-reset-dialog .h5p-inner {
|
||||
min-width: 316px;
|
||||
max-width: 400px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.h5p-embed-dialog .h5p-embed-code-container,
|
||||
.h5p-embed-size {
|
||||
resize: none;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
padding: 0.375em 0.5em 0.25em;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 1px 2px 0 #d0d0d0 inset;
|
||||
font-size: 0.875em;
|
||||
letter-spacing: 0.065em;
|
||||
font-family: sans-serif;
|
||||
white-space: pre;
|
||||
line-height: 1.5em;
|
||||
height: 2.0714em;
|
||||
background: #f5f5f5;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
.h5p-embed-dialog .h5p-embed-code-container:focus {
|
||||
height: 5em;
|
||||
}
|
||||
.h5p-embed-size {
|
||||
width: 3.5em;
|
||||
text-align: right;
|
||||
margin: 0.5em 0;
|
||||
line-height: 2em;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-scroll-content {
|
||||
border-top: 2.25em solid transparent;
|
||||
padding: 1em;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
color: #555555;
|
||||
z-index: 1;
|
||||
}
|
||||
.h5p-popup-dialog.h5p-open .h5p-scroll-content {
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar-track {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar-thumb {
|
||||
box-shadow: 0 0 10px #000 inset;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-close {
|
||||
cursor: pointer;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-close:after {
|
||||
font-family: 'H5P';
|
||||
content: "\e894";
|
||||
font-size: 2em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 1.125em;
|
||||
height: 1.125em;
|
||||
line-height: 1.125em;
|
||||
color: #656565;
|
||||
cursor: pointer;
|
||||
text-indent: -0.065em;
|
||||
z-index: 3
|
||||
}
|
||||
.h5p-popup-dialog .h5p-close:hover:after,
|
||||
.h5p-popup-dialog .h5p-close:focus:after {
|
||||
color: #454545;
|
||||
}
|
||||
.h5p-popup-dialog .h5p-close:active:after {
|
||||
color: #252525;
|
||||
}
|
||||
.h5p-poopup-dialog h2 {
|
||||
margin: 0.25em 0 0.5em;
|
||||
}
|
||||
.h5p-popup-dialog h3 {
|
||||
margin: 0.75em 0 0.25em;
|
||||
}
|
||||
.h5p-popup-dialog dl {
|
||||
margin: 0.25em 0 0.75em;
|
||||
}
|
||||
.h5p-popup-dialog dt {
|
||||
float: left;
|
||||
margin: 0 0.75em 0 0;
|
||||
}
|
||||
.h5p-popup-dialog dt:after {
|
||||
content: ':';
|
||||
}
|
||||
.h5p-popup-dialog dd {
|
||||
margin: 0;
|
||||
}
|
||||
.h5p-expander {
|
||||
cursor: pointer;
|
||||
font-size: 1.125em;
|
||||
outline: none;
|
||||
margin: 0.5em 0 0;
|
||||
display: inline-block;
|
||||
}
|
||||
.h5p-expander:before {
|
||||
content: "+";
|
||||
width: 1em;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
.h5p-expander.h5p-open:before {
|
||||
content: "-";
|
||||
text-indent: 0.125em;
|
||||
}
|
||||
.h5p-expander:hover,
|
||||
.h5p-expander:focus {
|
||||
color: #303030;
|
||||
}
|
||||
.h5p-expander:active {
|
||||
color: #202020;
|
||||
}
|
||||
.h5p-expander-content {
|
||||
display: none;
|
||||
}
|
||||
.h5p-expander-content p {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.h5p-content-copyrights {
|
||||
border-left: 0.25em solid #d0d0d0;
|
||||
margin-left: 0.25em;
|
||||
padding-left: 0.25em;
|
||||
}
|
||||
.h5p-throbber {
|
||||
background: url('../images/throbber.gif?ver=1.2.1') 10px center no-repeat;
|
||||
padding-left: 38px;
|
||||
min-height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.h5p-dialog-ok-button {
|
||||
cursor: default;
|
||||
float: right;
|
||||
outline: none;
|
||||
border: 2px solid #ccc;
|
||||
padding: 0.25em 0.75em 0.125em;
|
||||
background: #eee;
|
||||
}
|
||||
.h5p-dialog-ok-button:hover,
|
||||
.h5p-dialog-ok-button:focus {
|
||||
background: #fafafa;
|
||||
}
|
||||
.h5p-dialog-ok-button:active {
|
||||
background: #eeffee;
|
||||
}
|
||||
.h5p-big-button {
|
||||
line-height: 1.25;
|
||||
display: block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
padding: 1em 1em 1em 3.75em;
|
||||
text-align: left;
|
||||
border: 1px solid #dedede;
|
||||
background: linear-gradient(#ffffff, #f1f1f2);
|
||||
border-radius: 0.25em;
|
||||
}
|
||||
.h5p-big-button:before {
|
||||
font-family: 'h5p';
|
||||
content: "\e893";
|
||||
line-height: 1;
|
||||
font-size: 3em;
|
||||
color: #2747f7;
|
||||
position: absolute;
|
||||
left: 0.125em;
|
||||
top: 0.125em;
|
||||
}
|
||||
.h5p-copy-button:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.h5p-big-button:hover {
|
||||
border: 1px solid #2747f7;
|
||||
background: #eff1fe;
|
||||
}
|
||||
.h5p-big-button:active {
|
||||
border: 1px solid #dedede;
|
||||
background: #dfe4fe;
|
||||
}
|
||||
.h5p-button-title {
|
||||
color: #2747f7;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.h5p-button-description {
|
||||
color: #757575;
|
||||
}
|
||||
.h5p-horizontal-line-text {
|
||||
border-top: 1px solid #dadada;
|
||||
line-height: 1;
|
||||
color: #474747;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin: 1.25em 0;
|
||||
}
|
||||
.h5p-horizontal-line-text > span {
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
position: absolute;
|
||||
top: -1em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.h5p-toast {
|
||||
font-size: 0.75em;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
color: #fff;
|
||||
z-index: 110;
|
||||
position: absolute;
|
||||
padding: 0 0.5em;
|
||||
line-height: 2;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
.h5p-toast-disabled {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* This is loaded as part of Core and not Editor since this needs to be outside the editor iframe */
|
||||
.h5peditor-semi-fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 101;
|
||||
}
|
||||
iframe.h5peditor-semi-fullscreen {
|
||||
background: #fff;
|
||||
z-index: 100001;
|
||||
}
|
||||
|
||||
.h5p-content.using-mouse *:not(textarea):focus {
|
||||
outline: none !important;
|
||||
}
|
Loading…
Reference in New Issue