#!/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