#!/usr/bin/env python3 # -*- coding:utf-8 -*- from pscript import RawJS from flexx import flx from ipfsdocs.log import info class PlaceHolder(flx.VBox): CSS = """ .flx-PlaceHolder { background-color: #111111; color: white; } .placeholder-debug-bottom { background-color: black; color: white; position: absolute; bottom: 0; } .placeholder-debug-top { background-color: black; color: white; position: absolute; top: 0; } .placeholder-progressbar { max-height: 1em!important; } """ default_css_class = flx.StringProp("", settable=True) custom_css_class = flx.StringProp("", settable=True) hidden = flx.BoolProp(True, settable=True) description = flx.StringProp(settable=True) cid = flx.StringProp("", settable=True) webcid = flx.StringProp("", settable=True) filename = flx.StringProp("", settable=True) mimetype = flx.StringProp("", settable=True) url = flx.StringProp("", settable=True) # automatically set from cid, don't set it selected = flx.BoolProp(False, settable=True) highlight = flx.EnumProp(["HIGHLIGHT", "DISCRET", "NEUTRAL"], "NEUTRAL", settable=True) html = flx.StringProp(settable=True) visible = flx.BoolProp(True, settable=True) intersection_ratio = flx.FloatProp(0., settable=True) load = flx.BoolProp(True, settable=True) scale = flx.FloatProp(1, settable=True) translate = flx.FloatPairProp(settable=True) tags = flx.ListProp(settable=True) @flx.action def update_tags(self, tags): self.set_tags(tags) @flx.emitter def playing_video(self): return {} @flx.emitter def stopped_video(self): return {} @flx.emitter def ended_video(self): return {} @flx.reaction("visible") def pause_video_if_invisible(self): """Make sure no video continue playing when not visible""" if not self.visible: self.pause_video() @flx.reaction("html", "load") def update_body(self): if self.load: html = self.html else: html = ( '
' ' Loading...' '
' ) self.body.set_html(html) @flx.reaction("cid", "root.state.selection") def update_selected(self): return self.set_selected(self.cid in self.root.state.selection) def toggle_video(self): videos = self.node.querySelectorAll("video") for index in videos: video = videos[index] if video.paused: video.play() else: video.pause() def pause_video(self): videos = self.node.querySelectorAll("video") for index in videos: video = videos[index] video.pause() @flx.reaction("body.html") def setup_video_events(self): videos = self.node.querySelectorAll("video") for index in videos: video = videos[index] video.onended = self.ended_video video.onplay = self.playing_video video.onpause = self.stopped_video def init(self): self.setup_contextmenu() self.body = flx.Label(flex=1) self.progress = flx.ProgressBar( css_class="d-none", flex=0.1, ) global Hammer self.hammer = Hammer(self.node, { "touchAction": 'pan-y' # see https://github.com/hammerjs/hammer.js/issues/691 }) # pinch is usually disabled as it makes the element # blocking, and pan is limited to x-axis, so enable these pinch = RawJS("this.hammer.get('pinch')") pinch.set({"enable": True}) self.hammer.on("pinchstart", lambda e: self.pinchstart(e)) self.hammer.on("pinch", lambda e: self.pinch(e)) self.hammer.on("pinchend", lambda e: self.pinchend(e)) self.pinch_prev = None self.node.flx_parent = self self.debug = flx.Label() @flx.reaction def _debug(self): self.debug.set_html( "
".join( [ f"{tag['key']}={tag['value']}" for tag in (self.tags or []) ] ) ) def pinchstart(self, event): self.root.jssays.pinching_placeholder() def pinchend(self, event): self.root.jssays.pinched_placeholder() if event.scale < 1: self.reset_transformation() else: self.set_translate((event.deltaX, event.deltaY)) self.set_scale(event.scale) @flx.reaction("cid") def reset_transformation(self): self.set_translate((0, 0)) self.set_scale(1) self.apply_style("transform: translate(0px, 0px) scale(1);") def pinch(self, event): deltaX, deltaY = self.translate deltaX += event.deltaX deltaY += event.deltaY scale = self.scale * event.scale style = f"transform: translate({deltaX}px, {deltaY}px) scale({scale});" self.apply_style(style) @flx.reaction("cid", "root.state.downloadmanager.current_download", "root.state.downloadmanager.download_queue") def toggle_progressbar_on_downloading(self): if ( self.root.state.downloadmanager.current_download[0] == self.cid or self.cid in [ download[0] for download in self.root.state.downloadmanager.download_queue ] ): self.progress.set_css_class("placeholder-progressbar") else: self.progress.set_css_class("d-none") self.progress.set_value(0) @flx.reaction("root.state.downloadmanager.progress", "cid", "root.state.downloadmanager.current_download") def update_progress(self): if self.root.state.downloadmanager.current_download[0] == self.cid: self.progress.set_value(self.root.state.downloadmanager.progress) else: self.progress.set_value(0) @flx.reaction("default_css_class", "custom_css_class", "hidden", "selected", "root.frontend.selection_only", "highlight", "visible") def update_css_class_with_custom(self, *events): css = self.default_css_class if self.selected: css += " rounded-pill border" if self.root.frontend.range_mode: css += " border-success" else: css += " rounded" css += f" {self.custom_css_class}" if self.hidden or (self.root.frontend.selection_only and self.cid not in self.root.state.selection): css += " d-none" if self.highlight == "DISCRET": css += " discret" elif self.highlight == "HIGHLIGHT": css += " border-danger" self.set_css_class(css) def show(self): self.set_hidden(False) def hide(self): self.set_hidden(True) self.set_html("") def create_buttons(self): if self.url != "": buttons = [ {"text": "\uf0ed", "action": lambda: self.download(True)}, {"text": "\uf1e0", "action": self.share}, {"text": "\uf1f8", "action": self.ask_delete}, {"text": "\uf05a", "action": self.show_info}, ] else: buttons = [] return buttons def show_info(self): self.root.frontend.showmodal.message.set_html( "
".join( [ self.description ] + [ f"{tag['key']}={tag['value']}" for tag in self.tags ] ) ) self.root.frontend.showmodal.display() @flx.reaction("pointer_click") def toggle_selection(self, *events): if self.selected: self.remove_from_selection() else: self.put_to_selection() @flx.reaction("pointer_click") def update_focus(self, *events): self.root.state.set_focused_cid(self.cid) @flx.action def put_to_selection(self): self.root.state._mutate_selection({self.cid: True}, "insert") @flx.action def remove_from_selection(self): self.root.state._mutate_selection([self.cid], "remove") def share(self): self.root.jssays.share_one(self.cid, self.webcid, self.filename) def ask_delete(self): self.root.frontend.confirm.display( "1 \uf15b \uf061 \uf1f8", self.delete ) def delete(self): self.root.jssays.delete_one(self.cid) def download(self): self.root.state.downloadmanager.add_download( self.cid, self.filename, self.mimetype, ) @flx.emitter def contextmenu(self): return {} def setup_contextmenu(self): def oncontextmenu(e): self.contextmenu() global RadialMenu e.preventDefault() # https://github.com/victorqribeiro/radialMenu def setup_button(values): res = {"textShadowOffsetX": -1, "textShadowOffsetY": -1} res.update(values) return res radial = RadialMenu( { "fontSize": 20, "textColor": "#DDD", "shadowColor": "rgba(0,0,0,0.8)", "shadowBlur": 10, "shadowOffsetX": 0, "shadowOffsetY": 0, "textShadowColor": "black", "textShadowBlur": 5, "backgroundColor": {"gradient": "radial", "colors": {"0": "#555", "1":"#BBB"}}, "borderColor": {"gradient": "radial", "colors": {"0": "#888", "1":"#555"}}, "rotation": 0, "buttons": map(setup_button, self.create_buttons()), "hideCallback": self.root.jssays.contextmenu_exit, } ) radial.setPos(e.clientX - radial.w2, e.clientY - radial.h2) self.root.jssays.contextmenu() radial.show() self.node.oncontextmenu = oncontextmenu