space.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. <!-- FIXME modal -->
  2. <div class="modal" v-if="(duplicate_folders.length > 0)" v-cloak>
  3. <div class="modal-wrapper">
  4. <div class="modal-dialog">
  5. <button type="button" class="btn btn-icon btn-light btn-round close" v-on:click="duplicate_folders = []">
  6. <span class="icon icon-cross-1"></span>
  7. </button>
  8. <div class="modal-content">
  9. <div class="modal-body labels-inline">
  10. <div class="modal-section">
  11. <div class="form-group">
  12. <label>
  13. [[__("duplicate_destination")]]
  14. </label>
  15. </div>
  16. <div class="form-group">
  17. <select v-on:change="duplicate_folder_id=$event.target.value">
  18. <option v-for="f in duplicate_folders" value="{{f._id}}">{{f.name}}</option>
  19. </select>
  20. </div>
  21. <div class="form-group">
  22. <button class="btn btn-md btn-round btn-primary" v-on:click="duplicate_folder_confirm(); ">
  23. <span class="icon-label">[[__("ok")]]</span>
  24. </button>
  25. <button class="btn btn-md btn-round btn-darken pull-right" v-on:click="duplicate_folders = [];">
  26. <span class="icon-label">[[__("cancel")]]</span>
  27. </button>
  28. </div>
  29. </div>
  30. </div>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. <div v-cloak class="header-left" v-show="active_space_loaded">
  36. <div class="btn-group dark">
  37. <div class="pull-left">
  38. <a
  39. class="btn btn-stroke-darken btn-md btn-round btn-icon"
  40. title="[[__("home")]]" href="/spaces"
  41. v-if="(logged_in && !embedded && !active_space.parent_space_id && !guest_nickname)">
  42. <span class="icon icon-home"></span>
  43. </a>
  44. <a
  45. class="btn btn-stroke-darken btn-md btn-round btn-icon"
  46. title="[[__("parent_folder")]]"
  47. href="/folders/{{active_space.parent_space_id}}"
  48. v-if="(active_space.parent_space_id && !guest_nickname)">
  49. <span class="icon icon-arrow-left-light"></span>
  50. </a>
  51. <input class="input input-md input-transparent w-auto"
  52. id="space-title"
  53. v-model="active_space.name" name="title" v-on:keydown="save_space_keydown($event)"
  54. v-if="space_editing_title && logged_in" style="padding-right:0" v-focus>
  55. <span class="input input-md input-transparent w-auto"
  56. v-if="!space_editing_title && logged_in"
  57. v-on:click="edit_space_title()">{{active_space.name}}</span>
  58. <span v-if="!logged_in" class="btn btn-dark btn-round btn-md">{{active_space.name}}</span>
  59. <button class="btn btn-md btn-transparent btn-icon" v-if="space_editing_title" v-on:click="save_space_keydown()">
  60. <span class="icon icon-check"></span>
  61. </button>
  62. <div class="dropdown top left light" v-bind:class="{open: active_dropdown=='space'}">
  63. <button class="btn btn-md btn-icon btn-dark" v-on:click="activate_dropdown('space')">
  64. <span class="icon icon-triangle-down"></span></button>
  65. <div class="dropdown-menu" role="menu">
  66. <ul class="select-list">
  67. <li v-on:click="activate_access()" v-if="logged_in">
  68. <span>
  69. <span class="icon icon-sm icon-share"></span>
  70. <span>[[ __('share') ]]</span>
  71. </span>
  72. </li>
  73. <li v-on:click="edit_space_title()" v-if="logged_in">
  74. <span>
  75. <span class="icon icon-sm icon-tag"></span>
  76. <span>[[ __('rename') ]]</span>
  77. </span>
  78. </li>
  79. <li v-on:click="duplicate_space_into_folder()" v-if="logged_in">
  80. <span>
  81. <span class="icon icon-sm icon-duplicate"></span>
  82. <span>[[ __('duplicate') ]]</span>
  83. </span>
  84. </li>
  85. <li v-on:click="download_space()">
  86. <span>
  87. <span class="icon icon-sm icon-download"></span>
  88. <span>[[ __('download_space') ]]</span>
  89. </span>
  90. </li>
  91. <li v-on:click="activate_modal('support')" v-if="logged_in">
  92. <span>
  93. <span class="icon icon-sm icon-info"></span>
  94. <span>[[ __('support') ]]</span>
  95. </span>
  96. </li>
  97. </ul>
  98. </div>
  99. </div>
  100. </div>
  101. </div>
  102. <span class="btn btn-red btn-md" id="offline-indicator" v-bind:class="{offline: was_offline}" v-on:click="show_offline_help()">[[__("offline")]]</span>
  103. </div>
  104. <div v-cloak class="header-right" v-if="active_space_loaded">
  105. <span v-for="active_user in active_space_users" >
  106. <button
  107. class="member btn btn-md btn-round"
  108. v-cloak
  109. v-bind:class="{'btn-dark': !active_user.avatar_thumb_uri}"
  110. v-bind:style="{'background-image': 'url('+active_user.avatar_thumb_uri+')'}"
  111. v-bind:title="active_user.nickname + ' is online'">
  112. {{active_user.nickname}}
  113. </button>
  114. </span>
  115. <div class="btn-group dark" v-if="active_space_role!='viewer'">
  116. <button class="btn btn-md btn-transparent btn-icon" title="Start Presentation (others follow what you see)" v-on:click="toggle_present_mode()" v-bind:class="{open:present_mode}">
  117. <span class="icon icon-presentation"></span>
  118. </button>
  119. </div>
  120. <div class="btn-group dark round" v-if="zones.length">
  121. <button class="btn btn-md btn-transparent btn-icon" v-on:click="go_to_previous_zone()" title="[[__("Previous Zone")]]">
  122. <span class="icon icon-triangle-4-left"></span>
  123. </button>
  124. <button class="btn btn-md btn-divider"></button>
  125. <button class="btn btn-md btn-transparent btn-icon" v-on:click="go_to_next_zone()" title="[[__("Next Zone")]]">
  126. <span class="icon icon-triangle-4-right"></span>
  127. </button>
  128. </div>
  129. <!--button class="btn btn-md btn-dark btn-round btn-icon" v-on:click="download_space()" title="[[__("download_space")]]">
  130. <span class="icon icon-download"></span>
  131. </button-->
  132. <div class="btn-group dark" id="meta-toggle" style="margin-right:10px">
  133. <button class="btn btn-md btn-transparent btn-icon" v-on:click="toggle_meta()" title="[[__("chat")]]">
  134. <span class="jewel" style="color: white; background-color: red" v-if="meta_unseen>0">{{meta_unseen}}</span>
  135. <span class="icon icon-messages"></span>
  136. </button>
  137. </div>
  138. </div>
  139. {% include "./tool/toolbar-elements.html" %}
  140. {% include "./tool/toolbar-object.html" %}
  141. <div v-if="active_space && active_space_loaded">
  142. <div id="lasso"></div>
  143. <div class="snap-ruler-h" v-bind:style="{top:snap_ruler_y+'px'}"></div>
  144. <div class="snap-ruler-v" v-bind:style="{left:snap_ruler_x+'px'}"></div>
  145. <div class="space-empty" v-cloak v-if="active_view == 'space' && !present_mode && active_space_artifacts.length == 0">
  146. <div class="table-fake">
  147. <div class="cell">
  148. <p>Click anywhere to add content.<br>
  149. You can also drop images, sounds and video<br>
  150. or use copy and paste.</p>
  151. </div>
  152. </div>
  153. </div>
  154. </div>
  155. <div id="space-loading" v-cloak v-bind:class="{active:loading_space_id, active:global_spinner}">
  156. <div>
  157. <div class="spinner"></div>
  158. </div>
  159. </div>
  160. <div id="space" v-cloak
  161. v-if="active_view == 'space' && active_space_loaded"
  162. class="section board active mouse-{{mouse_state}} tool-{{active_tool}}"
  163. v-bind:style="{'background-color': active_space.background_color}"
  164. v-sd-droppable="handle_data_drop;active_space"
  165. v-sd-whiteboard
  166. v-on:scroll="handle_scroll"
  167. v-on:dblclick="handle_space_doubleclick">
  168. <div id="space-clipboard" style="position:fixed;top:0;left:0;z-index:0;opacity:0;background-color:white"><textarea v-model="selected_artifacts_json" cols="2" rows="2" id="clipboard-ta" class="mousetrap"></textarea></div>
  169. <div class="space-bounds" v-bind:style="{width: active_space.width*bounds_zoom + 'px', height: active_space.height*bounds_zoom + 'px', 'background-color': active_space.background_color}"></div>
  170. <div class="wrapper"
  171. v-bind:style="{
  172. transform: 'scale('+viewport_zoom+')',
  173. 'transform-origin': '0 0',
  174. width: active_space.width + 'px',
  175. height: active_space.height + 'px',
  176. 'background-image': (active_space.background_uri)?'url(' + active_space.background_uri + ')':'',
  177. 'background-color': ''+active_space.background_color,
  178. 'margin-left': bounds_margin_horiz + 'px',
  179. 'margin-top': bounds_margin_vert + 'px'}" >
  180. <div v-for="a in active_space_artifacts"
  181. v-bind:style="a.view.style" v-bind:class="a.view.classes"
  182. v-bind:class="{text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))}"
  183. id="artifact-{{a._id}}">
  184. <div v-if="a.view && a.view.major_type" style="height:100%; width:100%" v-bind:title="(a.editor_name || (a.user && a.user.nickname) || '')">
  185. <span v-if="a.locked && is_selected(a)" class="link-wrapper">
  186. <span class="btn btn-sm btn-icon btn-round btn-primary">
  187. <span class="icon icon-lock-closed"></span>
  188. </span>
  189. </span>
  190. <!-- text -->
  191. <div v-if="a.view.major_type == 'text'" class="text" v-bind:style="a.view.inner_style">
  192. <div class="text-table">
  193. <div class="text-cell" v-bind:style="a.view.text_cell_style">
  194. <div class="text-column" class="text-editing" v-sd-richtext:obj="a" v-show="editing_artifact_id==a._id">{{{a.description}}}</div>
  195. <div class="text-column" v-show="editing_artifact_id!=a._id">{{{a.description|urls_to_links}}}</div>
  196. </div>
  197. </div>
  198. <span v-if="a.view.link.length>0" class="link-wrapper">
  199. <a class="link btn btn-round btn-primary btn-sm" v-if="a.view.link" v-bind:href="a.view.link" target="_blank">{{a.view.link_caption}}</a>
  200. </span>
  201. <div class="btn btn-xs btn-icon btn-round btn-primary edit" v-show="editing_artifact_id!=a._id && is_selected(a)" v-on:touchstart="delayed_edit_artifact($event)"><span class="icon icon-pencil" v-on:click="toggle_selected_artifact_editing(true)" v-on:"touchstart:delayed_edit_artifact($event)"></span></div>
  202. <input v-show="is_selected(a)" type="text" id="ios-focuser-{{a._id}}" class="ios-focuser">
  203. </div>
  204. <!-- drawing (annotation) -->
  205. <div v-if="a.view.major_type == 'vector'" class="clip" v-bind:style="a.view.inner_style">
  206. <div>{{{a.view.vector_svg}}}</div>
  207. </div>
  208. <!-- svg shape -->
  209. <div v-if="a.view.major_type == 'shape'" class="clip" v-bind:style="a.view.inner_style">
  210. <div class="shape">{{{a.view.vector_svg}}}</div>
  211. <div class="text-table">
  212. <div class="text-cell" v-bind:style="a.view.text_cell_style">
  213. <div class="text-column" class="text-editing" v-sd-richtext:obj="a" v-show="editing_artifact_id==a._id"></div>
  214. <div class="text-column" v-show="editing_artifact_id!=a._id">{{{a.description|urls_to_links}}}</div>
  215. </div>
  216. </div>
  217. <span v-if="a.view.link.length>0" class="link-wrapper">
  218. <a class="link btn btn-round btn-primary btn-sm" v-if="a.view.link" v-bind:href="a.view.link" target="_blank">{{a.view.link_caption}}</a>
  219. </span>
  220. <div class="btn btn-xs btn-icon btn-round btn-primary edit" v-show="editing_artifact_id!=a._id && is_selected(a)" v-on:touchstart="delayed_edit_artifact($event)"><span class="icon icon-pencil" v-on:click="toggle_selected_artifact_editing(true)" v-on:"touchstart:delayed_edit_artifact($event)"></span></div>
  221. <input v-show="is_selected(a)" type="text" id="ios-focuser-{{a._id}}" class="ios-focuser">
  222. </div>
  223. <!-- svg image -->
  224. <div v-if="a.view.major_type == 'svg'" class="svg" v-bind:style="a.view.inner_style">
  225. <img v-bind:src="a.view.payload_uri"></img>
  226. </div>
  227. <!-- image -->
  228. <div v-if="a.view.major_type == 'image'" class="image" v-bind:style="a.view.inner_style + '; background-image: url('+a.view.thumbnail_uri+');'">
  229. <span class="title">{{a.title}}</span>
  230. <div class="spinner"></div>
  231. <div class="progress" v-bind:style="{width: a.view.progress+'%'}"></div>
  232. <div class="progress-text">{{a.description}}</div>
  233. <video v-if="a.mime == 'image/gif' && a.payload_alternatives && a.payload_alternatives.length > 0" preload autoplay loop>
  234. <source v-for="rep in a.payload_alternatives" v-bind:src="rep.payload_uri" v-bind:type="rep.mime" />
  235. </video>
  236. <a class="btn btn-md btn-icon btn-round btn-primary edit"
  237. v-show="a.mime == 'application/pdf'"
  238. v-bind:href="a.payload_uri" target="_blank"
  239. ><span class="icon icon-link"></span></a>
  240. <span v-if="a.view.link.length>0" class="link-wrapper">
  241. <a class="link btn btn-round btn-primary btn-sm" v-if="a.view.link" v-bind:href="a.view.link" target="_blank">{{a.view.link_caption}}</a>
  242. </span>
  243. </div>
  244. <!-- video -->
  245. <div v-if="a.view.major_type == 'video'" v-videoplayer="a" class="video" v-bind:style="a.view.inner_style + ';background-image: url('+a.view.thumbnail_uri+');'">
  246. <video preload="metadata" v-bind:poster="a.view.thumbnail_uri">
  247. <source v-for="rep in a.payload_alternatives" v-bind:src="rep.payload_uri" v-bind:type="rep.mime" />
  248. <source v-if="a.payload_uri && a.mime" v-bind:src="a.payload_uri" v-bind:type="a.mime" />
  249. </video>
  250. <div class="tl-controls">
  251. <div class="btn btn-md btn-toggle btn-round" v-bind:class="{alt:a.player_view.state=='playing'}">
  252. <span class="btn-option play">
  253. <span class="icon icon-controls-play"></span>
  254. </span>
  255. <span class="btn-option pause">
  256. <span class="icon icon-controls-pause"></span>
  257. </span>
  258. </div>
  259. <span class="btn btn-md btn-round btn-icon stop" v-show="a.player_view.state=='playing' || a.player_view.state=='paused'">
  260. <span class="icon icon-controls-stop"></span>
  261. </span>
  262. </div>
  263. <div class="spinner"></div>
  264. <div class="progress" v-bind:style="{width: a.view.progress+'%'}">{{a.description}}</div>
  265. </div>
  266. <!-- audio -->
  267. <div v-if="a.view.major_type == 'audio'" v-audioplayer="a" class="audio" v-bind:style="a.view.inner_style">
  268. <audio>
  269. <source v-for="alt in a.payload_alternatives | orderBy 'mime' -1" v-bind:src="alt.payload_uri" v-bind:type="alt.mime"/>
  270. <source v-bind:src="a.payload_uri" v-bind:type="a.mime" v-if="a.payload_uri"/>
  271. </audio>
  272. <div class="timeline" v-show="a.h>=64 && a.w>=170" v-bind:style="{'background-image': 'url(' + a.payload_thumbnail_web_uri +')'}">
  273. <div class="tl-current-time" v-bind:style="{width: a.player_view.current_time_float*100 + '%'}"></div>
  274. <div class="tl-inpoint" v-bind:style="{left: a.player_view.inpoint_float*100 + '%'}" v-if="a.player_view.inpoint_float>0.0"></div>
  275. <div class="tl-outpoint" v-bind:style="{left: a.player_view.outpoint_float*100 + '%'}"></div>
  276. </div>
  277. <div class="tl-controls">
  278. <div class="btn btn-md btn-toggle btn-round" v-bind:class="{alt:a.player_view.state=='playing'}">
  279. <span class="btn-option play">
  280. <span class="icon icon-controls-play"></span>
  281. </span>
  282. <span class="btn-option pause">
  283. <span class="icon icon-controls-pause"></span>
  284. </span>
  285. </div>
  286. <span class="btn btn-md btn-round btn-icon stop" v-show="a.player_view.state=='playing' || a.player_view.state=='paused'">
  287. <span class="icon icon-controls-stop"></span>
  288. </span>
  289. <span class="tl-title" v-show="a.w>=400">{{a.view.filename}}</span>
  290. <span class="tl-times" class="btn-group">
  291. <span class="btn btn-md btn-transparent no-p">{{a.player_view.current_time_string}}</span>
  292. <span class="btn btn-md btn-transparent no-p" v-show="a.w>=170"> / {{a.player_view.total_time_string}}</span>
  293. </span>
  294. <span v-show="logged_in && a.w>=310">
  295. <a class="btn btn-xs btn-round btn-icon set-inpoint" title="Set Inpoint at Playhead">
  296. <span class="icon icon-edge-left"></span>
  297. </a>
  298. <a class="btn btn-xs btn-round btn-icon set-outpoint" title="Set Outpoint at Playhead">
  299. <span class="icon icon-edge-right"></span>
  300. </a>
  301. <a class="btn btn-xs btn-round btn-icon reset-points" title="Reset In-/Outpoints">
  302. <span class="icon icon-size-horizontal"></span>
  303. </a>
  304. </span>
  305. </div>
  306. <div class="spinner"></div>
  307. <div class="progress" v-bind:style="{width: a.view.progress + '%'}">{{a.description}}</div>
  308. </div>
  309. <!-- zone -->
  310. <div v-if="a.view.major_type == 'zone'" class="zone" v-bind:style="a.view.inner_style">
  311. <div class="text-cell">
  312. <div class="text-column" class="text-editing" v-sd-richtext:obj="a" v-show="editing_artifact_id==a._id"></div>
  313. <div class="text-column" v-show="editing_artifact_id!=a._id">{{{a.description|urls_to_links}}}</div>
  314. </div>
  315. </div>
  316. <!-- embed -->
  317. <div v-if="a.view.major_type == 'oembed'" class="oembed"
  318. v-bind:style="{'background-image': 'url('+a.view.thumbnail_uri+')'}"
  319. v-bind:class="{interactive:(a.view.interactive || present_mode)}">
  320. {{{a.view.oembed_html}}}
  321. <button class="btn btn-icon btn-primary btn-round interact" v-on:click="a.view.interactive=1" v-if="!a.view.interactive && !present_mode"><span class="icon icon-tool-pointer"></span></button>
  322. </div>
  323. <!-- file -->
  324. <div v-if="a.view.major_type == 'file'" class="text" v-bind:style="a.view.inner_style">
  325. <span class="icon icon-page-vertical-double"></span>
  326. {{a.view.filename}}
  327. <div class="spinner"></div>
  328. </div>
  329. </div>
  330. </div>
  331. <!-- board artifacts end -->
  332. <div
  333. class="handles handles-object"
  334. v-bind:class="{'handles-vector':selection_metrics.vector_selection}"
  335. v-bind:style="selection_metrics.style">
  336. <div class="handle resize-nw"><div><div><div>
  337. <span class="value-h">{{selection_metrics.h}}</span>
  338. <span class="value-w">{{selection_metrics.w}}</span>
  339. </div></div></div></div>
  340. <div class="handle resize-n"><div><div><div>
  341. <span class="value-h">{{selection_metrics.h}}</span>
  342. </div></div></div></div>
  343. <div class="handle resize-ne"><div><div><div>
  344. <span class="value-h">{{selection_metrics.h}}</span>
  345. <span class="value-w">{{selection_metrics.w}}</span>
  346. </div></div></div></div>
  347. <div class="handle resize-e"><div><div><div>
  348. <span class="value-w">{{selection_metrics.w}}</span>
  349. </div></div></div></div>
  350. <div class="handle resize-se"><div><div><div>
  351. <span class="value-h">{{selection_metrics.h}}</span>
  352. <span class="value-w">{{selection_metrics.w}}</span>
  353. </div></div></div></div>
  354. <div class="handle resize-s"><div><div><div>
  355. <span class="value-h">{{selection_metrics.h}}</span>
  356. </div></div></div></div>
  357. <div class="handle resize-sw"><div><div><div>
  358. <span class="value-h">{{selection_metrics.h}}</span>
  359. <span class="value-w">{{selection_metrics.w}}</span>
  360. </div></div></div></div>
  361. <div class="handle resize-w"><div><div><div>
  362. <span class="value-w">{{selection_metrics.w}}</span>
  363. </div></div></div></div>
  364. <div class="edge-handle resize-n"> <span class="value-h">{{selection_metrics.h}}</span> </div>
  365. <div class="edge-handle resize-s"> <span class="value-h">{{selection_metrics.h}}</span> </div>
  366. <div class="edge-handle resize-e"> <span class="value-w">{{selection_metrics.w}}</span> </div>
  367. <div class="edge-handle resize-w"> <span class="value-w">{{selection_metrics.w}}</span> </div>
  368. <div
  369. v-bind:style="selection_metrics.vector_style" v-show="selection_metrics.vector_points">
  370. <span v-for="p in selection_metrics.vector_points" v-bind:style="{left: p.dx+'px', top: p.dy+'px'}" class="vector-handle" data-idx="{{$index}}"></span>
  371. </div>
  372. </div>
  373. <!-- handles end -->
  374. <div class="cursor" v-for="c in user_cursors" v-bind:style="{left: c.x + 'px', top: c.y+'px'}"><span class="btn btn-round btn-sm btn-dark"><span class="icon icon-tool-pointer"></span> {{c.name}}</div>
  375. </div>
  376. <!-- wrapper end -->
  377. </div>
  378. <div v-if="active_space_loaded" v-cloak>
  379. <div id="minimap"
  380. v-bind:style="{width: ''+(active_space.width/minimap_scale)+'px', height: ''+(active_space.height/minimap_scale)+'px', bottom: '66px', right: '20px'}"
  381. v-if="active_space"
  382. v-on:mousedown="handle_minimap_mousedown($event)"
  383. v-on:touchstart="handle_minimap_mousedown($event)"
  384. v-on:mousemove="handle_minimap_mousemove($event)"
  385. v-on:touchmove="handle_minimap_mousemove($event)"
  386. v-on:mouseleave="handle_minimap_mouseup($event)"
  387. v-on:touchend="handle_minimap_mouseup($event)"
  388. v-on:mouseup="handle_minimap_mouseup($event)">
  389. <div v-for="a in active_space_artifacts" v-bind:style="{left: ''+(a.x/minimap_scale)+ 'px', top: ''+(a.y/minimap_scale) + 'px', width: ''+(a.w/minimap_scale)+ 'px', height: ''+(a.h/minimap_scale) + 'px'}"></div>
  390. <div class="window" v-bind:style="{left: ''+(scroll_left/minimap_scale) + 'px', top: ''+(scroll_top/minimap_scale)+ 'px', width: ''+(window_width/minimap_scale)+ 'px', height: ''+(window_height/minimap_scale) + 'px'}"></div>
  391. </div>
  392. <div class="btn-group dark" style="position:absolute;bottom:20px;right:20px;">
  393. <button class="btn btn-icon btn-md btn-transparent" v-on:click="zoom_in()">
  394. <span class="icon icon-plus"></span>
  395. </button>
  396. <button class="btn btn-md btn-transparent no-p" v-on:click="zoom_to_original()">
  397. {{viewport_zoom_percent}}%
  398. </button>
  399. <button class="btn btn-icon btn-md btn-transparent" v-on:click="zoom_out()">
  400. <span class="icon icon-minus"></span>
  401. </button>
  402. </div>
  403. </div>