Merge pull request #8428 from highfidelity/discovery

Snapshot of Snapshot-Discovery to master
This commit is contained in:
Howard Stearns 2016-08-23 14:32:10 -07:00 committed by GitHub
commit 89b00883dd
43 changed files with 1408 additions and 293 deletions

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path d="M25.9,20.6c0.29,0.15,0.47,0.38,0.48,0.72c0,0.7,0.1,2.92,0.08,3.62c1,0.3,3.61,1.19,4.56,1.45
c0.15,0.01,0.27,0.03,0.38,0.09c0.2,0.1,0.33,0.3,0.36,0.58c0.01,0.11,0.02,0.22,0.02,0.33c-0.03,1.5,0.01,1.08-0.05,2.58
c-0.06,1.55-0.78,2.72-2.21,3.34c-1.12,0.49-2.01,0.57-2.92,0.1c-0.25-0.13-0.5-0.3-0.76-0.51c-1.46-1.21-2.95-2.39-4.42-3.59
c-0.37-0.3-0.48-0.75-0.29-1.15c0.19-0.39,0.63-0.61,1.05-0.49c0.06,0.02,0.12,0.04,0.18,0.07c0.1,0.05,0.19,0.11,0.27,0.18
c0.64,0.51,1,1.02,1.63,1.53c0,0,0.01,0,0.01,0.01c0.01,0.01,0.04,0.01,0.09,0.04c0-0.12,0.01-0.23,0.01-0.33
c0.06-2.53,0.12-5.06,0.18-7.59c0.01-0.36,0.06-0.69,0.37-0.92c0.28-0.2,0.58-0.25,0.89-0.12C25.85,20.57,25.88,20.58,25.9,20.6
M26.64,19.16c-0.06-0.03-0.12-0.06-0.18-0.09c-0.83-0.36-1.74-0.25-2.48,0.3c-1,0.73-1.03,1.83-1.04,2.19
c-0.04,1.54-0.07,3.11-0.11,4.64c-0.07-0.02-0.13-0.05-0.2-0.07c-1.17-0.34-2.41,0.22-2.96,1.33c-0.26,0.53-0.32,1.14-0.18,1.71
c0.14,0.55,0.45,1.03,0.91,1.4c0.49,0.4,0.98,0.8,1.46,1.18c0.97,0.78,1.97,1.59,2.94,2.4c0.35,0.29,0.7,0.52,1.05,0.71
c1.72,0.88,3.28,0.39,4.3-0.06c1.96-0.86,3.09-2.55,3.18-4.76c0.03-0.89,0.04-1.13,0.04-1.47c0-0.23,0-0.51,0.01-1.13
c0-0.18,0-0.37-0.03-0.56c-0.1-0.82-0.54-1.49-1.23-1.84c-0.25-0.13-0.53-0.21-0.82-0.25c-0.56-0.16-1.57-0.49-2.48-0.79
c-0.27-0.09-0.53-0.17-0.78-0.25c-0.01-0.34-0.02-0.7-0.03-1.06C28.02,22.12,28,21.58,28,21.31
C27.99,20.39,27.49,19.59,26.64,19.16L26.64,19.16z"/>
</g>
<path d="M33.4,17.76l-2.87-2.64l-0.13,1.65c-1.42-0.61-3.13-0.95-4.95-0.95c-1.91,0-3.66,0.37-5.06,1.04l-0.23-1.76l-2.73,2.79
l3.35,2l-0.22-1.66c1.27-0.71,3.01-1.11,4.89-1.11c1.8,0,3.52,0.37,4.84,1.03l-0.14,1.79L33.4,17.76z"/>
<g>
<path class="st0" d="M25.64,19.86c0.29,0.15,0.47,0.38,0.48,0.72c0,0.7,0.1,2.92,0.08,3.62c1,0.3,3.61,1.19,4.56,1.45
c0.16,0.04,0.28,0.08,0.39,0.13c0.2,0.1,0.31,0.26,0.35,0.54c0.01,0.11,0.02,0.22,0.02,0.33c-0.03,1.5,0.01,1.08-0.05,2.58
c-0.06,1.55-0.78,2.72-2.21,3.34c-1.12,0.49-2.01,0.57-2.92,0.1c-0.25-0.13-0.5-0.3-0.76-0.51c-1.46-1.21-2.95-2.39-4.42-3.59
c-0.37-0.3-0.48-0.75-0.29-1.15c0.19-0.39,0.63-0.61,1.05-0.49c0.06,0.02,0.12,0.04,0.18,0.07c0.1,0.05,0.19,0.11,0.27,0.18
c0.64,0.51,1,1.02,1.63,1.53c0,0,0.01,0,0.01,0.01c0.01,0.01,0.04,0.01,0.09,0.04c0-0.12,0.01-0.23,0.01-0.33
c0.06-2.53,0.12-5.06,0.18-7.59c0.01-0.36,0.06-0.69,0.37-0.92c0.28-0.2,0.58-0.25,0.89-0.12C25.59,19.83,25.62,19.84,25.64,19.86
M26.38,18.42c-0.06-0.03-0.12-0.06-0.18-0.09c-0.83-0.36-1.74-0.25-2.48,0.3c-1,0.73-1.03,1.83-1.04,2.19
c-0.04,1.54-0.07,3.11-0.11,4.64c-0.07-0.02-0.13-0.05-0.2-0.07c-1.17-0.34-2.41,0.22-2.96,1.33c-0.26,0.53-0.32,1.14-0.18,1.71
c0.14,0.55,0.45,1.03,0.91,1.4c0.49,0.4,0.98,0.8,1.46,1.18c0.97,0.78,1.97,1.59,2.94,2.4c0.35,0.29,0.7,0.52,1.05,0.71
c1.72,0.88,3.28,0.39,4.3-0.06c1.96-0.86,3.09-2.55,3.18-4.76c0.03-0.89,0.04-1.13,0.04-1.47c0-0.23,0-0.51,0.01-1.13
c0-0.18,0-0.37-0.03-0.56c-0.1-0.82-0.45-1.41-1.13-1.76c-0.25-0.13-0.61-0.24-0.91-0.32c-0.56-0.16-1.57-0.49-2.48-0.79
c-0.27-0.09-0.53-0.17-0.78-0.25c-0.01-0.34-0.02-0.7-0.03-1.06c-0.02-0.57-0.03-1.11-0.04-1.38
C27.73,19.65,27.23,18.85,26.38,18.42L26.38,18.42z"/>
</g>
<path class="st0" d="M33.14,17.02l-2.87-2.64l-0.13,1.65c-1.42-0.61-3.13-0.95-4.95-0.95c-1.91,0-3.66,0.37-5.06,1.04l-0.23-1.76
l-2.73,2.79l3.35,2l-0.22-1.66c1.27-0.71,3.01-1.11,4.89-1.11c1.8,0,3.52,0.37,4.84,1.03l-0.14,1.79L33.14,17.02z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -1,81 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="1440"
height="200"
data-icon="map-marker"
data-container-transform="translate(24)"
viewBox="0 0 1440 200"
id="svg4136"
inkscape:version="0.91 r13725"
sodipodi:docname="address-bar.svg">
<metadata
id="metadata4144">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4142" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1536"
inkscape:window-height="687"
id="namedview4140"
showgrid="false"
inkscape:zoom="0.61319416"
inkscape:cx="670.06567"
inkscape:cy="52.468468"
inkscape:window-x="105"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg4136" />
<rect
style="fill:#ededed;fill-opacity:1;stroke:none;stroke-linejoin:round;stroke-opacity:1"
id="rect4141"
width="1280"
height="140"
x="160"
y="30"
rx="16.025024"
ry="17.019567" />
<rect
style="fill:#dadada;fill-opacity:1;stroke:#cbcbcb;stroke-width:0.35830048;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="rect4135"
width="328.72031"
height="139.64169"
x="150.33546"
y="30.179144"
rx="18.876532"
ry="20.609974" />
<circle
style="fill:#b8b8b8;fill-opacity:1;stroke:none;stroke-opacity:1"
id="path4146"
cx="100"
cy="100"
r="100" />
<path
d="m 100,36.000005 c -22.1,0 -40,17.9 -40,39.999995 0,30 40,88 40,88 0,0 40,-58 40,-88 0,-22.099995 -17.9,-39.999995 -40,-39.999995 z m 0,22 c 9.9,0 18,8.099995 18,17.999995 0,9.9 -8.1,18 -18,18 -9.9,0 -18,-8.1 -18,-18 0,-9.9 8.1,-17.999995 18,-17.999995 z"
id="path4138"
inkscape:connector-curvature="0" />
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="svg4136" inkscape:version="0.91 r13725" sodipodi:docname="address-bar.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1440 200"
style="enable-background:new 0 0 1440 200;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1E1E1E;}
.st1{fill:#E6E7E8;}
.st2{fill:#FFFFFF;}
</style>
<path class="st0" d="M1428.61,172H11.46c-6.27,0-11.39-5.13-11.39-11.39V49.58c0-6.27,5.13-11.39,11.39-11.39h1417.15
c6.27,0,11.39,5.13,11.39,11.39v111.03C1440,166.87,1434.87,172,1428.61,172z"/>
<path class="st1" d="M1428.61,165.81H11.46c-6.27,0-11.39-5.13-11.39-11.39V43.39c0-6.27,5.13-11.39,11.39-11.39h1417.15
c6.27,0,11.39,5.13,11.39,11.39v111.03C1440,160.68,1434.87,165.81,1428.61,165.81z"/>
<path class="st2" d="M1133.24,165.81H417.95c-4.47,0-8.12-3.65-8.12-8.12V40.11c0-4.47,3.65-8.12,8.12-8.12h715.28
c4.47,0,8.12,3.65,8.12,8.12v117.57C1141.36,162.15,1137.7,165.81,1133.24,165.81z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<path class="st0" d="M33.72,85.08l-9.15-9.15l-0.74-0.74l0.74-0.74l9.35-9.35c0.59-0.59,0.59-1.56,0-2.15
c-0.29-0.29-0.67-0.45-1.08-0.45c-0.41,0-0.79,0.16-1.08,0.45L19.52,75.19l12.04,12.04c0.29,0.29,0.67,0.45,1.08,0.45
c0.41,0,0.79-0.16,1.08-0.45C34.31,86.64,34.31,85.67,33.72,85.08z"/>
<path class="st1" d="M33.72,33.45l-9.15-9.15l-0.74-0.74l0.74-0.74l9.35-9.35c0.59-0.59,0.59-1.56,0-2.15
c-0.29-0.29-0.67-0.45-1.08-0.45c-0.41,0-0.79,0.16-1.08,0.45L19.52,23.56L31.56,35.6c0.29,0.29,0.67,0.45,1.08,0.45
c0.41,0,0.79-0.16,1.08-0.45C34.31,35.01,34.31,34.04,33.72,33.45z"/>
<path class="st2" d="M17.99,124.82l12.78,12.78c1,1,2.63,1,3.63,0c1-1,1-2.63,0-3.63l-9.15-9.15l9.35-9.35c1-1,1-2.63,0-3.63
c-1-1-2.63-1-3.63,0L17.99,124.82z"/>
<path class="st3" d="M32.79,112.13c0.41,0,0.79,0.16,1.08,0.45c0.59,0.59,0.59,1.56,0,2.15l-9.35,9.35l-0.74,0.74l0.74,0.74
l9.15,9.15c0.59,0.59,0.59,1.56,0,2.15c-0.29,0.29-0.67,0.45-1.08,0.45c-0.41,0-0.79-0.16-1.08-0.45l-12.04-12.04l12.24-12.24
C32,112.29,32.38,112.13,32.79,112.13 M32.79,111.08c-0.66,0-1.31,0.25-1.82,0.75l-12.98,12.98l12.78,12.78
c0.5,0.5,1.16,0.75,1.82,0.75c0.66,0,1.31-0.25,1.82-0.75c1-1,1-2.63,0-3.63l-9.15-9.15l9.35-9.35c1-1,1-2.63,0-3.63
C34.1,111.34,33.44,111.08,32.79,111.08L32.79,111.08z"/>
</g>
<g id="Layer_2">
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<path class="st0" d="M21.12,62.95c-0.29-0.29-0.67-0.45-1.08-0.45c-0.41,0-0.79,0.16-1.08,0.45c-0.59,0.59-0.59,1.56,0,2.15
l9.35,9.35l0.74,0.74l-0.74,0.74l-9.15,9.15c-0.59,0.59-0.59,1.56,0,2.15c0.29,0.29,0.67,0.45,1.08,0.45
c0.41,0,0.79-0.16,1.08-0.45l12.04-12.04L21.12,62.95z"/>
<path class="st1" d="M21.12,11.32c-0.29-0.29-0.67-0.45-1.08-0.45c-0.41,0-0.79,0.16-1.08,0.45c-0.59,0.59-0.59,1.56,0,2.15
l9.35,9.35l0.74,0.74l-0.74,0.74l-9.15,9.15c-0.59,0.59-0.59,1.56,0,2.15c0.29,0.29,0.67,0.45,1.08,0.45
c0.41,0,0.79-0.16,1.08-0.45l12.04-12.04L21.12,11.32z"/>
<path class="st2" d="M34.9,124.82L22.11,137.6c-1,1-2.63,1-3.63,0c-1-1-1-2.63,0-3.63l9.15-9.15l-9.35-9.35c-1-1-1-2.63,0-3.63
c1-1,2.63-1,3.63,0L34.9,124.82z"/>
<path class="st3" d="M20.1,112.13c0.41,0,0.79,0.16,1.08,0.45l12.24,12.24l-12.04,12.04c-0.29,0.29-0.67,0.45-1.08,0.45
c-0.41,0-0.79-0.16-1.08-0.45c-0.59-0.59-0.59-1.56,0-2.15l9.15-9.15l0.74-0.74l-0.74-0.74l-9.35-9.35c-0.59-0.59-0.59-1.56,0-2.15
C19.31,112.29,19.69,112.13,20.1,112.13 M20.1,111.08c-0.66,0-1.31,0.25-1.82,0.75c-1,1-1,2.63,0,3.63l9.35,9.35l-9.15,9.15
c-1,1-1,2.63,0,3.63c0.5,0.5,1.16,0.75,1.82,0.75s1.31-0.25,1.82-0.75l12.78-12.78l-12.98-12.98
C21.41,111.34,20.76,111.08,20.1,111.08L20.1,111.08z"/>
</g>
<g id="Layer_2">
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<g>
<path class="st0" d="M36.75,71.26c-0.05-0.31-0.22-0.57-0.47-0.76l-10.72-7.73l-8.21,5.71l0,0l0,0l-2.98,1.99
c-0.26,0.17-0.43,0.43-0.5,0.74c-0.06,0.3,0,0.61,0.17,0.87c0.22,0.32,0.58,0.52,0.97,0.52c0.23,0,0.45-0.07,0.64-0.19l2.99-1.99
l6.62-4.61l0.28-0.2l0.28,0.2l9.11,6.57c0.2,0.14,0.43,0.22,0.68,0.22c0.37,0,0.73-0.18,0.94-0.48
C36.73,71.88,36.8,71.57,36.75,71.26z"/>
<path class="st0" d="M23.59,79.62c0-1.13,0.97-2.03,2.1-2.03c1.13,0,2.1,0.93,2.1,2.06v6.06h7.58v-8.97l-3.28-2.41l-6.46-4.68
l-6.88,4.64l-3.23,2.1v9.32h8.07V79.62z"/>
</g>
<g>
<path class="st1" d="M36.75,19.64c-0.05-0.31-0.22-0.57-0.47-0.76l-10.72-7.73l-8.21,5.71l0,0l0,0l-2.98,1.99
c-0.26,0.17-0.43,0.43-0.5,0.74c-0.06,0.3,0,0.61,0.17,0.87c0.22,0.32,0.58,0.52,0.97,0.52c0.23,0,0.45-0.07,0.64-0.19l2.99-1.99
l6.62-4.61l0.28-0.2l0.28,0.2l9.11,6.57c0.2,0.14,0.43,0.22,0.68,0.22c0.37,0,0.73-0.18,0.94-0.48
C36.73,20.25,36.8,19.94,36.75,19.64z"/>
<path class="st1" d="M23.59,27.99c0-1.13,0.97-2.03,2.1-2.03c1.13,0,2.1,0.93,2.1,2.06v6.06h7.58v-8.97l-3.28-2.41l-6.46-4.68
l-6.88,4.64l-3.23,2.1v9.32h8.07V27.99z"/>
</g>
<g>
<path class="st2" d="M15,122.61c-0.53,0-1.05-0.26-1.37-0.73c-0.5-0.76-0.3-1.78,0.46-2.28l2.98-1.99l8.5-5.91l11,7.93
c0.74,0.53,0.9,1.56,0.37,2.3c-0.53,0.74-1.56,0.9-2.3,0.37l-9.11-6.57l-6.63,4.61l-3,1.99C15.63,122.52,15.31,122.61,15,122.61z"
/>
<path class="st2" d="M35.6,122.69c-0.36,0-0.71-0.11-1.01-0.33l-9.07-6.53l-6.58,4.58l-3,2c-0.28,0.19-0.61,0.29-0.95,0.29
c-0.58,0-1.12-0.29-1.44-0.77c-0.53-0.79-0.31-1.87,0.48-2.39l2.98-1.99l8.54-5.94l11.05,7.96c0.37,0.27,0.62,0.67,0.69,1.12
c0.07,0.46-0.03,0.91-0.3,1.29c-0.27,0.37-0.67,0.62-1.12,0.69C35.79,122.68,35.7,122.69,35.6,122.69z M25.53,115.63l9.16,6.6
c0.34,0.24,0.75,0.34,1.17,0.28s0.77-0.29,1.02-0.63c0.24-0.34,0.34-0.75,0.28-1.17c-0.07-0.41-0.29-0.77-0.63-1.02l-10.96-7.9
l-8.45,5.88l-2.98,1.99c-0.72,0.48-0.91,1.45-0.44,2.17c0.29,0.44,0.78,0.7,1.3,0.7c0.31,0,0.61-0.09,0.87-0.26l3-1.99
L25.53,115.63z"/>
</g>
<path class="st3" d="M36.62,119.56l-11.05-7.96l-8.54,5.94l-2.98,1.99c-0.79,0.53-1.01,1.6-0.48,2.39
c0.32,0.48,0.86,0.77,1.44,0.77c0.34,0,0.67-0.1,0.95-0.29l3-2l6.58-4.58l9.07,6.53c0.3,0.21,0.64,0.33,1.01,0.33
c0.55,0,1.08-0.27,1.4-0.72C37.56,121.2,37.39,120.12,36.62,119.56z M17.39,118.07l8.17-5.68l10.68,7.7
c0.23,0.17,0.39,0.42,0.43,0.7c0.05,0.28-0.02,0.57-0.19,0.8c-0.2,0.28-0.53,0.45-0.88,0.45c-0.23,0-0.45-0.07-0.63-0.2l-9.44-6.8
l-6.95,4.83l-2.99,1.99c-0.18,0.12-0.38,0.18-0.6,0.18c-0.36,0-0.7-0.18-0.9-0.48c-0.16-0.24-0.22-0.53-0.16-0.81
c0.06-0.28,0.22-0.53,0.46-0.69L17.39,118.07z"/>
<path class="st2" d="M23.59,129.14c0-1.13,0.97-2.03,2.1-2.03c1.13,0,2.1,0.93,2.1,2.06v6.06h7.58v-8.97l-3.28-2.41l-6.46-4.68
l-6.88,4.64l-3.23,2.1v9.32h8.07V129.14z"/>
</g>
<g id="Layer_2">
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<path class="st0" d="M25.71,59.91c-4.83,0-8.75,3.92-8.75,8.75c0,4.83,8.75,16.5,8.75,16.5s8.75-11.67,8.75-16.5
C34.46,63.83,30.54,59.91,25.71,59.91z M25.71,72.53c-2.48,0-4.5-2.01-4.5-4.5c0-2.48,2.01-4.5,4.5-4.5s4.5,2.01,4.5,4.5
C30.2,70.52,28.19,72.53,25.71,72.53z"/>
<path class="st0" d="M36.43,86.1H14.28c-0.49,0-0.9,0.39-0.9,0.89c0,0.49,0.4,0.89,0.9,0.89h22.15c0.49,0,0.9-0.39,0.9-0.89
C37.32,86.49,36.92,86.1,36.43,86.1z"/>
<path class="st1" d="M25.71,8.28c-4.83,0-8.75,3.92-8.75,8.75c0,4.83,8.75,16.5,8.75,16.5s8.75-11.67,8.75-16.5
C34.46,12.2,30.54,8.28,25.71,8.28z M25.71,20.91c-2.48,0-4.5-2.01-4.5-4.5c0-2.48,2.01-4.5,4.5-4.5s4.5,2.01,4.5,4.5
C30.2,18.89,28.19,20.91,25.71,20.91z"/>
<path class="st1" d="M36.43,34.47H14.28c-0.49,0-0.9,0.39-0.9,0.89s0.4,0.89,0.9,0.89h22.15c0.49,0,0.9-0.39,0.9-0.89
S36.92,34.47,36.43,34.47z"/>
<path class="st2" d="M25.71,109.66c-4.83,0-8.75,3.92-8.75,8.75c0,4.83,8.75,16.5,8.75,16.5s8.75-11.67,8.75-16.5
C34.46,113.57,30.54,109.66,25.71,109.66z M25.71,122.28c-2.48,0-4.5-2.01-4.5-4.5c0-2.48,2.01-4.5,4.5-4.5s4.5,2.01,4.5,4.5
C30.2,120.27,28.19,122.28,25.71,122.28z"/>
<path class="st2" d="M36.43,138.33H14.28c-0.85,0-1.54-0.69-1.54-1.54s0.69-1.54,1.54-1.54h22.15c0.85,0,1.54,0.69,1.54,1.54
S37.28,138.33,36.43,138.33z"/>
<path class="st3" d="M36.43,135.89c0.49,0,0.9,0.4,0.9,0.9c0,0.49-0.4,0.9-0.9,0.9H14.28c-0.49,0-0.9-0.4-0.9-0.9
c0-0.49,0.4-0.9,0.9-0.9H36.43 M36.43,135.25H14.28c-0.85,0-1.54,0.69-1.54,1.54c0,0.85,0.69,1.54,1.54,1.54h22.15
c0.85,0,1.54-0.69,1.54-1.54C37.97,135.94,37.28,135.25,36.43,135.25L36.43,135.25z"/>
</g>
<g id="Layer_2">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<circle class="st0" cx="26.54" cy="69.8" r="4.44"/>
<path class="st0" d="M34.87,82.89c0.01-0.01,0.02-0.02,0.03-0.03v-1.86c0-2.68-2.33-4.77-5-4.77h-6.42c-2.68,0-4.85,2.09-4.85,4.77
v1.88H34.87z"/>
<path class="st0" d="M44.17,67.05c0-3.97-3.22-7.19-7.19-7.19H16.67c-3.97,0-7.19,3.22-7.19,7.19v14.18c0,3.97,3.22,7.19,7.19,7.19
h20.31c3.97,0,7.19-3.22,7.19-7.19V67.05z M42.39,81.32c0,3.03-2.46,5.49-5.49,5.49H16.58c-3.03,0-5.49-2.46-5.49-5.49v-14.2
c0-3.03,2.46-5.49,5.49-5.49h20.33c3.03,0,5.49,2.46,5.49,5.49V81.32z"/>
<circle class="st1" cx="26.54" cy="18.17" r="4.44"/>
<path class="st1" d="M34.87,31.26c0.01-0.01,0.02-0.02,0.03-0.03v-1.86c0-2.68-2.33-4.77-5-4.77h-6.42c-2.68,0-4.85,2.09-4.85,4.77
v1.88H34.87z"/>
<path class="st1" d="M44.17,15.42c0-3.97-3.22-7.19-7.19-7.19H16.67c-3.97,0-7.19,3.22-7.19,7.19V29.6c0,3.97,3.22,7.19,7.19,7.19
h20.31c3.97,0,7.19-3.22,7.19-7.19V15.42z M42.39,29.69c0,3.03-2.46,5.49-5.49,5.49H16.58c-3.03,0-5.49-2.46-5.49-5.49V15.5
c0-3.03,2.46-5.49,5.49-5.49h20.33c3.03,0,5.49,2.46,5.49,5.49V29.69z"/>
<circle class="st2" cx="26.52" cy="119.45" r="4.44"/>
<path class="st2" d="M34.85,132.54c0.01-0.01,0.02-0.02,0.03-0.03v-1.86c0-2.68-2.33-4.77-5-4.77h-6.42
c-2.68,0-4.85,2.09-4.85,4.77v1.88H34.85z"/>
<g>
<path class="st2" d="M36.9,138.76H16.6c-4.32,0-7.83-3.51-7.83-7.83v-14.16c0-4.32,3.51-7.83,7.83-7.83h20.3
c4.32,0,7.83,3.51,7.83,7.83v14.16C44.73,135.25,41.22,138.76,36.9,138.76z M16.6,111.93c-2.67,0-4.84,2.17-4.84,4.84v14.16
c0,2.67,2.17,4.84,4.84,4.84h20.3c2.67,0,4.84-2.17,4.84-4.84v-14.16c0-2.67-2.17-4.84-4.84-4.84H16.6z"/>
<path class="st3" d="M36.9,109.58c3.96,0,7.19,3.22,7.19,7.19v14.16c0,3.96-3.22,7.19-7.19,7.19H16.6c-3.96,0-7.19-3.22-7.19-7.19
v-14.16c0-3.96,3.22-7.19,7.19-7.19H36.9 M16.6,136.42h20.3c3.02,0,5.49-2.46,5.49-5.49v-14.16c0-3.02-2.46-5.49-5.49-5.49H16.6
c-3.02,0-5.49,2.46-5.49,5.49v14.16C11.11,133.95,13.57,136.42,16.6,136.42 M36.9,108.93H16.6c-4.32,0-7.83,3.51-7.83,7.83v14.16
c0,4.32,3.51,7.83,7.83,7.83h20.3c4.32,0,7.83-3.51,7.83-7.83v-14.16C44.73,112.45,41.22,108.93,36.9,108.93L36.9,108.93z
M16.6,135.77c-2.67,0-4.84-2.17-4.84-4.84v-14.16c0-2.67,2.17-4.84,4.84-4.84h20.3c2.67,0,4.84,2.17,4.84,4.84v14.16
c0,2.67-2.17,4.84-4.84,4.84H16.6L16.6,135.77z"/>
</g>
</g>
<g id="Layer_2">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -14,6 +14,8 @@ import "controls"
import "styles"
import "windows"
import "hifi"
import "hifi/toolbars"
import "controls-uit" as HifiControls
Window {
id: root
@ -45,50 +47,80 @@ Window {
anchors.centerIn = parent;
}
function resetAfterTeleport() {
storyCardFrame.shown = root.shown = false;
}
function goCard(card) {
addressLine.text = card.userStory.name;
if (addressBarDialog.useFeed) {
storyCardHTML.url = addressBarDialog.metaverseServerUrl + "/user_stories/" + card.storyId;
storyCardFrame.shown = true;
return;
}
addressLine.text = card.hifiUrl;
toggleOrGo(true);
}
property var allDomains: [];
property var suggestionChoices: [];
property var domainsBaseUrl: null;
property var allPlaces: [];
property var allStories: [];
property int cardWidth: 200;
property int cardHeight: 152;
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
AddressBarDialog {
id: addressBarDialog
implicitWidth: backgroundImage.width
implicitHeight: backgroundImage.height
// The buttons have their button state changed on hover, so we have to manually fix them up here
onBackEnabledChanged: backArrow.buttonState = addressBarDialog.backEnabled ? 1 : 0;
onForwardEnabledChanged: forwardArrow.buttonState = addressBarDialog.forwardEnabled ? 1 : 0;
onUseFeedChanged: updateFeedState();
onReceivedHifiSchemeURL: resetAfterTeleport();
Row {
ListModel { id: suggestions }
ListView {
id: scroll
width: backgroundImage.width;
height: cardHeight;
spacing: hifi.layout.spacing;
clip: true;
anchors {
bottom: backgroundImage.top;
bottomMargin: 2 * hifi.layout.spacing;
right: backgroundImage.right;
rightMargin: -104; // FIXME
horizontalCenter: backgroundImage.horizontalCenter
}
spacing: hifi.layout.spacing;
Card {
id: s0;
model: suggestions;
orientation: ListView.Horizontal;
delegate: Card {
width: cardWidth;
height: cardHeight;
goFunction: goCard
goFunction: goCard;
userName: model.username;
placeName: model.place_name;
hifiUrl: model.place_name + model.path;
imageUrl: model.image_url;
thumbnail: model.thumbnail_url;
action: model.action;
timestamp: model.created_at;
onlineUsers: model.online_users;
storyId: model.metaverseId;
hoverThunk: function () { ListView.view.currentIndex = index; }
unhoverThunk: function () { ListView.view.currentIndex = -1; }
}
Card {
id: s1;
width: cardWidth;
height: cardHeight;
goFunction: goCard
}
Card {
id: s2;
width: cardWidth;
height: cardHeight;
goFunction: goCard
highlightMoveDuration: -1;
highlightMoveVelocity: -1;
highlight: Rectangle { color: "transparent"; border.width: 4; border.color: "#1DB5ED"; z: 1; }
leftMargin: 50; // Start the first item over by about the same amount as the last item peeks through on the other side.
rightMargin: 50;
}
Image { // Just a visual indicator that the user can swipe the cards over to see more.
source: "../images/Swipe-Icon-single.svg"
width: 50;
visible: suggestions.count > 3;
anchors {
right: scroll.right;
verticalCenter: scroll.verticalCenter;
}
}
Image {
id: backgroundImage
source: "../images/address-bar.svg"
@ -97,64 +129,43 @@ Window {
property int inputAreaHeight: 56.0 * root.scale // Height of the background's input area
property int inputAreaStep: (height - inputAreaHeight) / 2
Image {
ToolbarButton {
id: homeButton
source: "../images/home-button.svg"
width: 29
height: 26
imageURL: "../images/home.svg"
buttonState: 1
defaultState: 1
hoverState: 2
onClicked: addressBarDialog.loadHome();
anchors {
left: parent.left
leftMargin: parent.height + 2 * hifi.layout.spacing
leftMargin: homeButton.width / 2
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
addressBarDialog.loadHome()
}
}
}
Image {
id: backArrow
source: addressBarDialog.backEnabled ? "../images/left-arrow.svg" : "../images/left-arrow-disabled.svg"
width: 22
height: 26
ToolbarButton {
id: backArrow;
imageURL: "../images/backward.svg";
hoverState: addressBarDialog.backEnabled ? 2 : 0;
defaultState: addressBarDialog.backEnabled ? 1 : 0;
buttonState: addressBarDialog.backEnabled ? 1 : 0;
onClicked: addressBarDialog.loadBack();
anchors {
left: homeButton.right
leftMargin: 2 * hifi.layout.spacing
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
addressBarDialog.loadBack()
}
}
}
Image {
id: forwardArrow
source: addressBarDialog.forwardEnabled ? "../images/right-arrow.svg" : "../images/right-arrow-disabled.svg"
width: 22
height: 26
ToolbarButton {
id: forwardArrow;
imageURL: "../images/forward.svg";
hoverState: addressBarDialog.forwardEnabled ? 2 : 0;
defaultState: addressBarDialog.forwardEnabled ? 1 : 0;
buttonState: addressBarDialog.forwardEnabled ? 1 : 0;
onClicked: addressBarDialog.loadForward();
anchors {
left: backArrow.right
leftMargin: 2 * hifi.layout.spacing
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
addressBarDialog.loadForward()
}
}
}
// FIXME replace with TextField
@ -162,20 +173,80 @@ Window {
id: addressLine
focus: true
anchors {
fill: parent
leftMargin: parent.height + parent.height + hifi.layout.spacing * 7
rightMargin: hifi.layout.spacing * 2
top: parent.top
bottom: parent.bottom
left: forwardArrow.right
right: placesButton.left
leftMargin: forwardArrow.width
rightMargin: placesButton.width
topMargin: parent.inputAreaStep + hifi.layout.spacing
bottomMargin: parent.inputAreaStep + hifi.layout.spacing
}
font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75
helperText: "Go to: place, @user, /path, network address"
helperPixelSize: font.pixelSize * 0.75
helperItalic: true
onTextChanged: filterChoicesByText()
}
// These two are radio buttons.
ToolbarButton {
id: placesButton
imageURL: "../images/places.svg"
buttonState: 1
defaultState: addressBarDialog.useFeed ? 0 : 1;
hoverState: addressBarDialog.useFeed ? 2 : -1;
onClicked: addressBarDialog.useFeed ? toggleFeed() : identity()
anchors {
right: feedButton.left;
bottom: addressLine.bottom;
}
}
ToolbarButton {
id: feedButton;
imageURL: "../images/snap-feed.svg";
buttonState: 0
defaultState: addressBarDialog.useFeed ? 1 : 0;
hoverState: addressBarDialog.useFeed ? -1 : 2;
onClicked: addressBarDialog.useFeed ? identity() : toggleFeed();
anchors {
right: parent.right;
bottom: addressLine.bottom;
rightMargin: feedButton.width / 2
}
}
}
Window {
width: 938;
height: 625;
scale: 0.8 // Reset scale of Window to 1.0 (counteract address bar's scale value of 1.25)
HifiControls.WebView {
anchors.fill: parent;
id: storyCardHTML;
}
id: storyCardFrame;
shown: false;
destroyOnCloseButton: false;
pinnable: false;
anchors {
verticalCenter: backgroundImage.verticalCenter;
horizontalCenter: scroll.horizontalCenter;
}
}
}
function toggleFeed() {
addressBarDialog.useFeed = !addressBarDialog.useFeed;
updateFeedState();
}
function updateFeedState() {
placesButton.buttonState = addressBarDialog.useFeed ? 0 : 1;
feedButton.buttonState = addressBarDialog.useFeed ? 1 : 0;
filterChoicesByText();
}
function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
// TODO: make available to other .qml.
var request = new XMLHttpRequest();
@ -200,133 +271,217 @@ Window {
request.open("GET", url, true);
request.send();
}
// call iterator(element, icb) once for each element of array, and then cb(error) when icb(error) has been called by each iterator.
// short-circuits if error. Note that iterator MUST be an asynchronous function. (Use setTimeout if necessary.)
function asyncEach(array, iterator, cb) {
var count = array.length;
function icb(error) {
if (!--count || error) {
count = -1; // don't cb multiple times (e.g., if error)
cb(error);
}
}
function asyncMap(array, iterator, cb) {
// call iterator(element, icb) once for each element of array, and then cb(error, mappedResult)
// when icb(error, mappedElement) has been called by each iterator.
// Calls to iterator are overlapped and may call icb in any order, but the mappedResults are collected in the same
// order as the elements of the array.
// Short-circuits if error. Note that iterator MUST be an asynchronous function. (Use setTimeout if necessary.)
var count = array.length, results = [];
if (!count) {
return cb();
return cb(null, results);
}
array.forEach(function (element) {
iterator(element, icb);
array.forEach(function (element, index) {
if (count < 0) { // don't keep iterating after we short-circuit
return;
}
iterator(element, function (error, mapped) {
results[index] = mapped;
if (error || !--count) {
count = 0; // don't cb multiple times if error
cb(error, results);
}
});
});
}
// Example:
/*asyncMap([0, 1, 2, 3, 4, 5, 6], function (elt, icb) {
console.log('called', elt);
setTimeout(function () {
console.log('answering', elt);
icb(null, elt);
}, Math.random() * 1000);
}, console.log); */
function identity(x) {
return x;
}
function addPictureToDomain(domainInfo, cb) { // asynchronously add thumbnail and lobby to domainInfo, if available, and cb(error)
// This requests data for all the names at once, and just uses the first one to come back.
// We might change this to check one at a time, which would be less requests and more latency.
asyncEach([domainInfo.name].concat(domainInfo.names || null).filter(identity), function (name, icb) {
var url = "https://metaverse.highfidelity.com/api/v1/places/" + name;
getRequest(url, function (error, json) {
var previews = !error && json.data.place.previews;
if (previews) {
if (!domainInfo.thumbnail) { // just grab the first one
domainInfo.thumbnail = previews.thumbnail;
}
if (!domainInfo.lobby) {
domainInfo.lobby = previews.lobby;
}
}
icb(error);
});
}, cb);
function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey
if (!error && (data.status === 'success')) {
return;
}
if (!error) { // Create a message from the data
error = data.status + ': ' + data.error;
}
if (typeof(error) === 'string') { // Make a proper Error object
error = new Error(error);
}
error.message += ' in ' + url; // Include the url.
cb(error);
return true;
}
function getDomains(options, cb) { // cb(error, arrayOfData)
if (!options.page) {
options.page = 1;
}
if (!domainsBaseUrl) {
var domainsOptions = [
'open', // published hours handle now
'active', // has at least one person connected. FIXME: really want any place that is verified accessible.
// FIXME: really want places I'm allowed in, not just open ones.
'restriction=open', // Not by whitelist, etc. FIXME: If logged in, add hifi to the restriction options, in order to include places that require login.
// FIXME add maturity
'protocol=' + encodeURIComponent(AddressManager.protocolVersion()),
'sort_by=users',
'sort_order=desc',
];
domainsBaseUrl = "https://metaverse.highfidelity.com/api/v1/domains/all?" + domainsOptions.join('&');
}
var url = domainsBaseUrl + "&page=" + options.page + "&users=" + options.minUsers + "-" + options.maxUsers;
getRequest(url, function (error, json) {
if (!error && (json.status !== 'success')) {
error = new Error("Bad response: " + JSON.stringify(json));
}
if (error) {
error.message += ' for ' + url;
return cb(error);
}
var domains = json.data.domains;
if (json.current_page < json.total_pages) {
options.page++;
return getDomains(options, function (error, others) {
cb(error, domains.concat(others));
});
}
cb(null, domains);
});
}
function filterChoicesByText() {
function fill1(target, data) {
if (!data) {
target.visible = false;
function getPlace(placeData, cb) { // cb(error, side-effected-placeData), after adding path, thumbnails, and description
var url = metaverseBase + 'places/' + placeData.place_name;
getRequest(url, function (error, data) {
if (handleError(url, error, data, cb)) {
return;
}
console.log('suggestion:', JSON.stringify(data));
target.userStory = data;
target.image.source = data.lobby || target.defaultPicture;
target.placeText = data.name;
target.usersText = data.online_users + ((data.online_users === 1) ? ' user' : ' users');
target.visible = true;
}
var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity);
var filtered = !words.length ? suggestionChoices : allDomains.filter(function (domain) {
var text = domain.names.concat(domain.tags).join(' ');
if (domain.description) {
text += domain.description;
var place = data.data.place, previews = place.previews;
placeData.path = place.path;
if (previews && previews.thumbnail) {
placeData.thumbnail_url = previews.thumbnail;
}
text = text.toUpperCase();
return words.every(function (word) {
return text.indexOf(word) >= 0;
if (place.description) {
placeData.description = place.description;
placeData.searchText += ' ' + place.description.toUpperCase();
}
cb(error, placeData);
});
}
function makeModelData(data, optionalPlaceName) { // create a new obj from data
// ListModel elements will only ever have those properties that are defined by the first obj that is added.
// So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story.
var name = optionalPlaceName || data.place_name,
tags = data.tags || [data.action, data.username],
description = data.description || "",
thumbnail_url = data.thumbnail_url || "",
image_url = thumbnail_url;
if (data.details) {
try {
image_url = JSON.parse(data.details).image_url || thumbnail_url;
} catch (e) {
console.log(name, "has bad details", data.details);
}
}
return {
place_name: name,
username: data.username || "",
path: data.path || "",
created_at: data.created_at || "",
action: data.action || "",
thumbnail_url: thumbnail_url,
image_url: image_url,
metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity.
tags: tags,
description: description,
online_users: data.online_users || 0,
searchText: [name].concat(tags, description || []).join(' ').toUpperCase()
}
}
function mapDomainPlaces(domain, cb) { // cb(error, arrayOfDomainPlaceData)
function addPlace(name, icb) {
getPlace(makeModelData(domain, name), icb);
}
// IWBNI we could get these results in order with most-recent-entered first.
// In any case, we don't really need to preserve the domain.names order in the results.
asyncMap(domain.names || [], addPlace, cb);
}
function suggestable(place) {
if (addressBarDialog.useFeed) {
return true;
}
return (place.place_name !== AddressManager.hostname) // Not our entry, but do show other entry points to current domain.
&& place.thumbnail_url
&& place.online_users // at least one present means it's actually online
&& place.online_users <= 20;
}
function getDomainPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model
// Each page of results is processed completely before we start on the next page.
// For each page of domains, we process each domain in parallel, and for each domain, process each place name in parallel.
// This gives us minimum latency within the page, but we do preserve the order within the page by using asyncMap and
// only appending the collected results.
var params = [
'open', // published hours handle now
// TBD: should determine if place is actually running?
'restriction=open', // Not by whitelist, etc. TBD: If logged in, add hifi to the restriction options, in order to include places that require login?
// TBD: add maturity?
'protocol=' + encodeURIComponent(AddressManager.protocolVersion()),
'sort_by=users',
'sort_order=desc',
'page=' + pageNumber
];
var url = metaverseBase + 'domains/all?' + params.join('&');
getRequest(url, function (error, data) {
if (handleError(url, error, data, cb)) {
return;
}
asyncMap(data.data.domains, mapDomainPlaces, function (error, pageResults) {
if (error) {
return cb(error);
}
// pageResults is now [ [ placeDataOneForDomainOne, placeDataTwoForDomainOne, ...], [ placeDataTwoForDomainTwo...] ]
pageResults.forEach(function (domainResults) {
allPlaces = allPlaces.concat(domainResults);
if (!addressLine.text && !addressBarDialog.useFeed) { // Don't add if the user is already filtering
domainResults.forEach(function (place) {
if (suggestable(place)) {
suggestions.append(place);
}
});
}
});
if (data.current_page < data.total_pages) {
return getDomainPage(pageNumber + 1, cb);
}
cb();
});
});
fill1(s0, filtered[0]);
fill1(s1, filtered[1]);
fill1(s2, filtered[2]);
}
function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model
var url = metaverseBase + 'user_stories?page=' + pageNumber;
getRequest(url, function (error, data) {
if (handleError(url, error, data, cb)) {
return;
}
var stories = data.user_stories.map(function (story) { // explicit single-argument function
return makeModelData(story);
});
allStories = allStories.concat(stories);
if (!addressLine.text && addressBarDialog.useFeed) { // Don't add if the user is already filtering
stories.forEach(function (story) {
suggestions.append(story);
});
}
if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now
return getUserStoryPage(pageNumber + 1, cb);
}
cb();
});
}
function filterChoicesByText() {
suggestions.clear();
var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity),
data = addressBarDialog.useFeed ? allStories : allPlaces;
function matches(place) {
if (!words.length) {
return suggestable(place);
}
return words.every(function (word) {
return place.searchText.indexOf(word) >= 0;
});
}
data.forEach(function (place) {
if (matches(place)) {
suggestions.append(place);
}
});
}
function fillDestinations() {
allDomains = suggestionChoices = [];
getDomains({minUsers: 0, maxUsers: 20}, function (error, domains) {
if (error) {
console.log('domain query failed:', error);
return filterChoicesByText();
}
var here = AddressManager.hostname; // don't show where we are now.
allDomains = domains.filter(function (domain) { return domain.name !== here; });
// Whittle down suggestions to those that have at least one user, and try to get pictures.
suggestionChoices = allDomains.filter(function (domain) { return domain.online_users; });
asyncEach(domains, addPictureToDomain, function (error) {
if (error) {
console.log('place picture query failed:', error);
}
// Whittle down more by requiring a picture.
suggestionChoices = suggestionChoices.filter(function (domain) { return domain.lobby; });
filterChoicesByText();
});
allPlaces = [];
allStories = [];
suggestions.clear();
getDomainPage(1, function (error) {
console.log('domain query', error || 'ok', allPlaces.length);
});
getUserStoryPage(1, function (error) {
console.log('user stories query', error || 'ok', allStories.length);
});
}

View file

@ -21,6 +21,8 @@ Original.Button {
width: 120
height: hifi.dimensions.controlLineHeight
HifiConstants { id: hifi }
style: ButtonStyle {
background: Rectangle {

View file

@ -12,6 +12,8 @@ Original.TextInput {
verticalAlignment: Original.TextInput.AlignVCenter
font.family: hifi.fonts.fontFamily
font.pixelSize: hifi.fonts.pixelSize
property int helperPixelSize: font.pixelSize
property bool helperItalic: false
/*
Original.Rectangle {
@ -23,7 +25,8 @@ Original.TextInput {
*/
Text {
anchors.fill: parent
font.pixelSize: parent.font.pixelSize
font.pixelSize: helperPixelSize
font.italic: helperItalic
font.family: parent.font.family
verticalAlignment: parent.verticalAlignment
horizontalAlignment: parent.horizontalAlignment

View file

@ -17,28 +17,69 @@ import QtGraphicalEffects 1.0
import "../styles-uit"
Rectangle {
property string userName: "";
property string placeName: "";
property string action: "";
property string timestamp: "";
property string hifiUrl: "";
property string thumbnail: defaultThumbnail;
property string imageUrl: "";
property var goFunction: null;
property var userStory: null;
property alias image: lobby;
property alias placeText: place.text;
property alias usersText: users.text;
property string storyId: "";
property string timePhrase: pastTime(timestamp);
property string actionPhrase: makeActionPhrase(action);
property int onlineUsers: 0;
property bool isUserStory: userName && !onlineUsers;
property int textPadding: 20;
property int textSize: 24;
property string defaultPicture: "../../images/default-domain.gif";
property int textSizeSmall: 18;
property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif");
HifiConstants { id: hifi }
function pastTime(timestamp) { // Answer a descriptive string
timestamp = new Date(timestamp);
var then = timestamp.getTime(),
now = Date.now(),
since = now - then,
ONE_MINUTE = 1000 * 60,
ONE_HOUR = ONE_MINUTE * 60,
hours = since / ONE_HOUR,
minutes = (hours % 1) * 60;
if (hours > 24) {
return timestamp.toDateString();
}
if (hours > 1) {
return Math.floor(hours).toString() + ' hr ' + Math.floor(minutes) + ' min ago';
}
if (minutes >= 2) {
return Math.floor(minutes).toString() + ' min ago';
}
return 'about a minute ago';
}
function makeActionPhrase(actionLabel) {
switch (actionLabel) {
case "snapshot":
return "took a snapshot";
default:
return "unknown"
}
}
Image {
id: lobby;
width: parent.width;
height: parent.height;
source: defaultPicture;
source: thumbnail || defaultThumbnail;
fillMode: Image.PreserveAspectCrop;
// source gets filled in later
anchors.verticalCenter: parent.verticalCenter;
anchors.left: parent.left;
onStatusChanged: {
if (status == Image.Error) {
console.log("source: " + source + ": failed to load " + JSON.stringify(userStory));
source = defaultPicture;
console.log("source: " + source + ": failed to load " + hifiUrl);
source = defaultThumbnail;
}
}
}
@ -69,6 +110,7 @@ Rectangle {
}
RalewaySemiBold {
id: place;
text: isUserStory ? "" : placeName;
color: hifi.colors.white;
size: textSize;
anchors {
@ -79,7 +121,8 @@ Rectangle {
}
RalewayRegular {
id: users;
size: textSize;
text: isUserStory ? timePhrase : (onlineUsers + ((onlineUsers === 1) ? ' person' : ' people'));
size: textSizeSmall;
color: hifi.colors.white;
anchors {
bottom: parent.bottom;
@ -87,10 +130,18 @@ Rectangle {
margins: textPadding;
}
}
// These two can be supplied to provide hover behavior.
// For example, AddressBarDialog provides functions that set the current list view item
// to that which is being hovered over.
property var hoverThunk: function () { };
property var unhoverThunk: function () { };
MouseArea {
id: zmouseArea;
anchors.fill: parent;
acceptedButtons: Qt.LeftButton;
onClicked: goFunction(parent);
hoverEnabled: true;
onEntered: hoverThunk();
onExited: unhoverThunk();
}
}

View file

@ -2296,7 +2296,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
} else if (isOption && !isShifted && !isMeta) {
Menu::getInstance()->triggerOption(MenuOption::ScriptEditor);
} else if (!isOption && !isShifted && isMeta) {
takeSnapshot();
takeSnapshot(true);
}
break;
@ -4913,6 +4913,7 @@ bool Application::canAcceptURL(const QString& urlString) const {
bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
if (urlString.startsWith(HIFI_URL_SCHEME)) {
// this is a hifi URL - have the AddressManager handle it
emit receivedHifiSchemeURL(urlString);
QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString",
Qt::AutoConnection, Q_ARG(const QString&, urlString));
return true;
@ -5189,15 +5190,24 @@ void Application::toggleLogDialog() {
}
}
void Application::takeSnapshot() {
QMediaPlayer* player = new QMediaPlayer();
QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav");
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
player->play();
void Application::takeSnapshot(bool notify, float aspectRatio) {
postLambdaEvent([notify, aspectRatio, this] {
QMediaPlayer* player = new QMediaPlayer();
QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav");
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
player->play();
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot());
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio));
emit DependencyManager::get<WindowScriptingInterface>()->snapshotTaken(path);
emit DependencyManager::get<WindowScriptingInterface>()->snapshotTaken(path, notify);
});
}
void Application::shareSnapshot(const QString& path) {
postLambdaEvent([path] {
// not much to do here, everything is done in snapshot code...
Snapshot::uploadSnapshot(path);
});
}
float Application::getRenderResolutionScale() const {

View file

@ -250,6 +250,9 @@ public:
float getAvatarSimrate() const { return _avatarSimCounter.rate(); }
float getAverageSimsPerSecond() const { return _simCounter.rate(); }
void takeSnapshot(bool notify, float aspectRatio = 0.0f);
void shareSnapshot(const QString& filename);
model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; }
gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; }
@ -276,6 +279,7 @@ signals:
void activeDisplayPluginChanged();
void uploadRequest(QString path);
void receivedHifiSchemeURL(const QString& url);
public slots:
QVector<EntityItemID> pasteEntities(float x, float y, float z);
@ -396,8 +400,6 @@ private:
int sendNackPackets();
void takeSnapshot();
MyAvatar* getMyAvatar() const;
void checkSkeleton() const;

View file

@ -26,6 +26,11 @@ bool AccountScriptingInterface::isLoggedIn() {
return accountManager->isLoggedIn();
}
bool AccountScriptingInterface::checkAndSignalForAccessToken() {
auto accountManager = DependencyManager::get<AccountManager>();
return accountManager->checkAndSignalForAccessToken();
}
QString AccountScriptingInterface::getUsername() {
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->isLoggedIn()) {

View file

@ -26,6 +26,7 @@ public slots:
static AccountScriptingInterface* getInstance();
QString getUsername();
bool isLoggedIn();
bool checkAndSignalForAccessToken();
};
#endif // hifi_AccountScriptingInterface_h

View file

@ -17,6 +17,8 @@
#include "Application.h"
#include "MainWindow.h"
#include <display-plugins/CompositorHelper.h>
#include <DependencyManager.h>
#include <OffscreenUi.h>
int DesktopScriptingInterface::getWidth() {
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
@ -31,3 +33,11 @@ void DesktopScriptingInterface::setOverlayAlpha(float alpha) {
qApp->getApplicationCompositor().setAlpha(alpha);
}
void DesktopScriptingInterface::show(const QString& path, const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "show", Qt::QueuedConnection, Q_ARG(QString, path), Q_ARG(QString, title));
return;
}
DependencyManager::get<OffscreenUi>()->show(path, title);
}

View file

@ -23,6 +23,7 @@ class DesktopScriptingInterface : public QObject, public Dependency {
public:
Q_INVOKABLE void setOverlayAlpha(float alpha);
Q_INVOKABLE void show(const QString& path, const QString& title);
int getWidth();
int getHeight();

View file

@ -26,3 +26,8 @@ void DialogsManagerScriptingInterface::toggleAddressBar() {
QMetaObject::invokeMethod(DependencyManager::get<DialogsManager>().data(),
"toggleAddressBar", Qt::QueuedConnection);
}
void DialogsManagerScriptingInterface::showFeed() {
QMetaObject::invokeMethod(DependencyManager::get<DialogsManager>().data(),
"showFeed", Qt::QueuedConnection);
}

View file

@ -18,6 +18,7 @@ class DialogsManagerScriptingInterface : public QObject {
Q_OBJECT
public:
DialogsManagerScriptingInterface();
Q_INVOKABLE void showFeed();
public slots:
void toggleAddressBar();

View file

@ -96,7 +96,7 @@ void WindowScriptingInterface::alert(const QString& message) {
/// \param const QString& message message to display
/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise
QScriptValue WindowScriptingInterface::confirm(const QString& message) {
return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message)));
return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No)));
}
/// Display a prompt with a text box
@ -203,3 +203,11 @@ void WindowScriptingInterface::copyToClipboard(const QString& text) {
qDebug() << "Copying";
QApplication::clipboard()->setText(text);
}
void WindowScriptingInterface::takeSnapshot(bool notify, float aspectRatio) {
qApp->takeSnapshot(notify, aspectRatio);
}
void WindowScriptingInterface::shareSnapshot(const QString& path) {
qApp->shareSnapshot(path);
}

View file

@ -55,12 +55,15 @@ public slots:
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
void showAssetServer(const QString& upload = "");
void copyToClipboard(const QString& text);
void takeSnapshot(bool notify = true, float aspectRatio = 0.0f);
void shareSnapshot(const QString& path);
signals:
void domainChanged(const QString& domainHostname);
void svoImportRequested(const QString& url);
void domainConnectionRefused(const QString& reasonMessage, int reasonCode);
void snapshotTaken(const QString& path);
void snapshotTaken(const QString& path, bool notify);
void snapshotShared(const QString& error);
private slots:
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);

View file

@ -38,6 +38,8 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
});
_backEnabled = !(DependencyManager::get<AddressManager>()->getBackStack().isEmpty());
_forwardEnabled = !(DependencyManager::get<AddressManager>()->getForwardStack().isEmpty());
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::setUseFeed, this, &AddressBarDialog::setUseFeed);
connect(qApp, &Application::receivedHifiSchemeURL, this, &AddressBarDialog::receivedHifiSchemeURL);
}
void AddressBarDialog::loadAddress(const QString& address, bool fromSuggestions) {

View file

@ -14,21 +14,29 @@
#define hifi_AddressBarDialog_h
#include <OffscreenQmlDialog.h>
#include <NetworkingConstants.h>
class AddressBarDialog : public OffscreenQmlDialog {
Q_OBJECT
HIFI_QML_DECL
Q_PROPERTY(bool backEnabled READ backEnabled NOTIFY backEnabledChanged)
Q_PROPERTY(bool forwardEnabled READ forwardEnabled NOTIFY forwardEnabledChanged)
Q_PROPERTY(bool useFeed READ useFeed WRITE setUseFeed NOTIFY useFeedChanged)
Q_PROPERTY(QString metaverseServerUrl READ metaverseServerUrl)
public:
AddressBarDialog(QQuickItem* parent = nullptr);
bool backEnabled() { return _backEnabled; }
bool forwardEnabled() { return _forwardEnabled; }
bool useFeed() { return _useFeed; }
void setUseFeed(bool useFeed) { if (_useFeed != useFeed) { _useFeed = useFeed; emit useFeedChanged(); } }
QString metaverseServerUrl() { return NetworkingConstants::METAVERSE_SERVER_URL.toString(); }
signals:
void backEnabledChanged();
void forwardEnabledChanged();
void useFeedChanged();
void receivedHifiSchemeURL(const QString& url);
protected:
void displayAddressOfflineMessage();
@ -42,6 +50,7 @@ protected:
bool _backEnabled;
bool _forwardEnabled;
bool _useFeed { false };
};
#endif

View file

@ -54,6 +54,11 @@ void DialogsManager::showAddressBar() {
AddressBarDialog::show();
}
void DialogsManager::showFeed() {
AddressBarDialog::show();
emit setUseFeed(true);
}
void DialogsManager::toggleDiskCacheEditor() {
maybeCreateDialog(_diskCacheEditor);
_diskCacheEditor->toggle();

View file

@ -45,6 +45,7 @@ public:
public slots:
void toggleAddressBar();
void showAddressBar();
void showFeed();
void toggleDiskCacheEditor();
void toggleLoginDialog();
void showLoginDialog();
@ -63,6 +64,7 @@ public slots:
signals:
void addressBarToggled();
void addressBarShown(bool visible);
void setUseFeed(bool useFeed);
private slots:
void hmdToolsClosed();

View file

@ -32,6 +32,7 @@
#include "Application.h"
#include "Snapshot.h"
#include "SnapshotUploader.h"
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
// %1 <= username, %2 <= date and time, %3 <= current location
@ -141,3 +142,34 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) {
return imageTempFile;
}
}
void Snapshot::uploadSnapshot(const QString& filename) {
const QString SNAPSHOT_UPLOAD_URL = "/api/v1/snapshots";
static SnapshotUploader uploader;
QFile* file = new QFile(filename);
Q_ASSERT(file->exists());
file->open(QIODevice::ReadOnly);
QHttpPart imagePart;
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"image\"; filename=\"" + file->fileName() + "\""));
imagePart.setBodyDevice(file);
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
multiPart->append(imagePart);
auto accountManager = DependencyManager::get<AccountManager>();
JSONCallbackParameters callbackParams(&uploader, "uploadSuccess", &uploader, "uploadFailure");
accountManager->sendRequest(SNAPSHOT_UPLOAD_URL,
AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation,
callbackParams,
nullptr,
multiPart);
}

View file

@ -40,6 +40,7 @@ public:
static Setting::Handle<QString> snapshotsLocation;
static Setting::Handle<bool> hasSetSnapshotsLocation;
static void uploadSnapshot(const QString& filename);
private:
static QFile* savedFileForSnapshot(QImage & image, bool isTemporary);
};

View file

@ -0,0 +1,75 @@
//
// SnapshotUploader.cpp
// interface/src/ui
//
// Created by Howard Stearns on 8/22/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonArray>
#include <AddressManager.h>
#include "scripting/WindowScriptingInterface.h"
#include "SnapshotUploader.h"
void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
const QString STORY_UPLOAD_URL = "/api/v1/user_stories";
static SnapshotUploader uploader;
// parse the reply for the thumbnail_url
QByteArray contents = reply.readAll();
QJsonParseError jsonError;
auto doc = QJsonDocument::fromJson(contents, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
auto dataObject = doc.object().value("data").toObject();
QString thumbnailUrl = dataObject.value("thumbnail_url").toString();
QString imageUrl = dataObject.value("image_url").toString();
auto addressManager = DependencyManager::get<AddressManager>();
QString placeName = addressManager->getPlaceName();
if (placeName.isEmpty()) {
placeName = addressManager->getHost();
}
QString currentPath = addressManager->currentPath(true);
// create json post data
QJsonObject rootObject;
QJsonObject userStoryObject;
QJsonObject detailsObject;
detailsObject.insert("image_url", imageUrl);
QString pickledDetails = QJsonDocument(detailsObject).toJson();
userStoryObject.insert("details", pickledDetails);
userStoryObject.insert("thumbnail_url", thumbnailUrl);
userStoryObject.insert("place_name", placeName);
userStoryObject.insert("path", currentPath);
userStoryObject.insert("action", "snapshot");
rootObject.insert("user_story", userStoryObject);
auto accountManager = DependencyManager::get<AccountManager>();
JSONCallbackParameters callbackParams(&uploader, "createStorySuccess", &uploader, "createStoryFailure");
accountManager->sendRequest(STORY_UPLOAD_URL,
AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation,
callbackParams,
QJsonDocument(rootObject).toJson());
}
else {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(contents);
}
}
void SnapshotUploader::uploadFailure(QNetworkReply& reply) {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(reply.readAll());
}
void SnapshotUploader::createStorySuccess(QNetworkReply& reply) {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(QString());
}
void SnapshotUploader::createStoryFailure(QNetworkReply& reply) {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(reply.readAll());
}

View file

@ -0,0 +1,26 @@
//
// SnapshotUploader.h
// interface/src/ui
//
// Created by Howard Stearns on 8/22/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SnapshotUploader_h
#define hifi_SnapshotUploader_h
#include <QObject>
#include <QtNetwork/QNetworkReply>
class SnapshotUploader : public QObject {
Q_OBJECT
public slots:
void uploadSuccess(QNetworkReply& reply);
void uploadFailure(QNetworkReply& reply);
void createStorySuccess(QNetworkReply& reply);
void createStoryFailure(QNetworkReply& reply);
};
#endif // hifi_SnapshotUploader_h

View file

@ -31,6 +31,6 @@ void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) {
}
}
QImage NullDisplayPlugin::getScreenshot() const {
QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const {
return QImage();
}

View file

@ -18,7 +18,7 @@ public:
glm::uvec2 getRecommendedRenderSize() const override;
bool hasFocus() const override;
void submitFrame(const gpu::FramePointer& newFrame) override;
QImage getScreenshot() const override;
QImage getScreenshot(float aspectRatio = 0.0f) const override;
private:
static const QString NAME;
};

View file

@ -659,12 +659,26 @@ void OpenGLDisplayPlugin::withMainThreadContext(std::function<void()> f) const {
_container->makeRenderingContextCurrent();
}
QImage OpenGLDisplayPlugin::getScreenshot() const {
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
auto size = _compositeFramebuffer->getSize();
if (isHmd()) {
size.x /= 2;
}
auto bestSize = size;
uvec2 corner(0);
if (aspectRatio != 0.0f) { // Pick out the largest piece of the center that produces the requested width/height aspectRatio
if (ceil(size.y * aspectRatio) < size.x) {
bestSize.x = round(size.y * aspectRatio);
} else {
bestSize.y = round(size.x / aspectRatio);
}
corner.x = round((size.x - bestSize.x) / 2.0f);
corner.y = round((size.y - bestSize.y) / 2.0f);
}
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
QImage screenshot(size.x, size.y, QImage::Format_ARGB32);
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
withMainThreadContext([&] {
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(uvec2(0), size), screenshot);
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
});
return screenshot.mirrored(false, true);
}

View file

@ -54,7 +54,7 @@ public:
return getSurfaceSize();
}
QImage getScreenshot() const override;
QImage getScreenshot(float aspectRatio = 0.0f) const override;
float presentRate() const override;

View file

@ -38,6 +38,7 @@ class AddressManager : public QObject, public Dependency {
Q_PROPERTY(QString protocol READ getProtocol)
Q_PROPERTY(QString hostname READ getHost)
Q_PROPERTY(QString pathname READ currentPath)
Q_PROPERTY(QString placename READ getPlaceName)
public:
Q_INVOKABLE QString protocolVersion();
using PositionGetter = std::function<glm::vec3()>;

View file

@ -172,7 +172,7 @@ public:
}
// Fetch the most recently displayed image as a QImage
virtual QImage getScreenshot() const = 0;
virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0;
// will query the underlying hmd api to compute the most recent head pose
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }

View file

@ -176,8 +176,7 @@ void showOpenVrKeyboard(bool show = true) {
}
}
void finishOpenVrKeyboardInput() {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
void updateFromOpenVrKeyboardInput() {
auto chars = _overlay->GetKeyboardText(textArray, 8192);
auto newText = QString(QByteArray(textArray, chars));
_keyboardFocusObject->setProperty("text", newText);
@ -187,6 +186,11 @@ void finishOpenVrKeyboardInput() {
//QInputMethodEvent event(_existingText, QList<QInputMethodEvent::Attribute>());
//event.setCommitString(newText, 0, _existingText.size());
//qApp->sendEvent(_keyboardFocusObject, &event);
}
void finishOpenVrKeyboardInput() {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
updateFromOpenVrKeyboardInput();
// Simulate an enter press on the top level window to trigger the action
if (0 == (_currentHints & Qt::ImhMultiLine)) {
qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n")));
@ -267,6 +271,11 @@ void handleOpenVrEvents() {
activeHmd->AcknowledgeQuit_Exiting();
break;
case vr::VREvent_KeyboardCharInput:
// Make the focused field match the keyboard results, inclusive of combining characters and such.
updateFromOpenVrKeyboardInput();
break;
case vr::VREvent_KeyboardDone:
finishOpenVrKeyboardInput();

View file

@ -27,4 +27,6 @@ Script.load("system/controllers/grab.js");
Script.load("system/controllers/teleport.js");
Script.load("system/controllers/toggleAdvancedMovementForHandControllers.js")
Script.load("system/dialTone.js");
Script.load("system/firstPersonHMD.js");
Script.load("system/firstPersonHMD.js");
Script.load("system/snapshot.js");

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#FFFFFF;}
.st2{fill:#1E1E1E;}
.st3{fill:#333333;}
</style>
<g id="Layer_2">
<g>
<g>
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
</g>
</g>
<g>
<g>
<path class="st0" d="M50,196.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V196.1z"/>
</g>
</g>
<g>
<g>
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
</g>
</g>
<g>
<path class="st2" d="M50,96.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.1z"/>
</g>
</g>
<path class="st1" d="M33.2,114.1H32v-0.9c0-1.6-2-2.1-3.6-2.1h-7c-1.6,0-2.4,0.5-2.4,2.1v0.9h-3c-1.6,0-3,0.8-3,2.4v10.7
c0,1.6,1.3,2.9,3,2.9h17.2c1.6,0,3.8-1.7,3.8-3.3v-10.3C37,114.9,34.8,114.1,33.2,114.1z M24.7,128.1c-3.8,0-6.8-3.1-6.8-6.8
c0-3.8,3.1-6.8,6.8-6.8c3.8,0,6.8,3.1,6.8,6.8C31.5,125,28.5,128.1,24.7,128.1z"/>
<g>
<path class="st1" d="M17.3,137.8c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2c-0.2-0.1-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2c0.1,0.1,0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3c0.3,0.1,0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9s-0.3,0.4-0.5,0.6c-0.2,0.1-0.5,0.3-0.8,0.3s-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
c-0.5-0.1-0.9-0.3-1.3-0.6l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2s-0.4-0.1-0.7-0.2
c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.4-0.2-0.6-0.3c-0.2-0.1-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4c0.3-0.1,0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2c0.4,0.1,0.7,0.3,1,0.5L17.3,137.8z"/>
<path class="st1" d="M20.5,138.4v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L20.5,138.4z"/>
<path class="st1" d="M25.7,142.5l2.5-6.4h1l2.5,6.4h-1.3l-0.6-1.6h-2.2l-0.6,1.6H25.7z M28.8,137.5l-0.9,2.6h1.8L28.8,137.5z"/>
<path class="st1" d="M32.7,142.5v-6.4h2.7c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.3,0,0.5-0.1,0.8c-0.1,0.3-0.2,0.5-0.4,0.7c-0.2,0.2-0.4,0.4-0.6,0.5c-0.2,0.1-0.5,0.2-0.8,0.2h-1.5v2.1H32.7z M33.9,139.3h1.4
c0.2,0,0.4-0.1,0.6-0.3c0.2-0.2,0.2-0.4,0.2-0.8c0-0.2,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
c-0.1,0-0.2-0.1-0.3-0.1h-1.4V139.3z"/>
</g>
<path class="st1" d="M33.2,64.1H32v-1c0-1.6-2-2-3.6-2h-7c-1.6,0-2.4,0.4-2.4,2v1h-3c-1.6,0-3,0.7-3,2.3v10.7c0,1.6,1.3,3,3,3h17.2
c1.6,0,3.8-1.8,3.8-3.4V66.4C37,64.8,34.8,64.1,33.2,64.1z M24.7,78c-3.8,0-6.8-3.1-6.8-6.8c0-3.8,3.1-6.8,6.8-6.8
c3.8,0,6.8,3.1,6.8,6.8C31.5,74.9,28.5,78,24.7,78z"/>
<g>
<path class="st1" d="M17.3,87.7c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2c-0.2-0.1-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2c0.1,0.1,0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3c0.3,0.1,0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9s-0.3,0.4-0.5,0.6c-0.2,0.1-0.5,0.3-0.8,0.3s-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
c-0.5-0.1-0.9-0.3-1.3-0.6l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3C16.2,90,16,90,15.8,89.9s-0.4-0.1-0.7-0.2
c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.4-0.2-0.6-0.3c-0.2-0.1-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4C15,86,15.3,86,15.7,86c0.5,0,0.9,0.1,1.2,0.2c0.4,0.1,0.7,0.3,1,0.5L17.3,87.7z"/>
<path class="st1" d="M20.5,88.3v4.1h-1.2V86h1l3.3,4.2V86h1.2v6.4h-1L20.5,88.3z"/>
<path class="st1" d="M25.7,92.4l2.5-6.4h1l2.5,6.4h-1.3l-0.6-1.6h-2.2l-0.6,1.6H25.7z M28.8,87.4L27.8,90h1.8L28.8,87.4z"/>
<path class="st1" d="M32.7,92.4V86h2.7c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.3,0,0.5-0.1,0.8c-0.1,0.3-0.2,0.5-0.4,0.7c-0.2,0.2-0.4,0.4-0.6,0.5c-0.2,0.1-0.5,0.2-0.8,0.2h-1.5v2.1H32.7z M33.9,89.2h1.4
c0.2,0,0.4-0.1,0.6-0.3c0.2-0.2,0.2-0.4,0.2-0.8c0-0.2,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
c-0.1,0-0.2-0.1-0.3-0.1h-1.4V89.2z"/>
</g>
<path class="st1" d="M33.2,164.1H32v-0.8c0-1.6-2-2.2-3.6-2.2h-7c-1.6,0-2.4,0.6-2.4,2.2v0.8h-3c-1.6,0-3,0.9-3,2.5v10.7
c0,1.6,1.3,2.8,3,2.8h17.2c1.6,0,3.8-1.6,3.8-3.2v-10.3C37,165,34.8,164.1,33.2,164.1z M24.7,178.2c-3.8,0-6.8-3.1-6.8-6.8
s3.1-6.8,6.8-6.8c3.8,0,6.8,3.1,6.8,6.8S28.5,178.2,24.7,178.2z"/>
<g>
<path class="st1" d="M17.3,187.9c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2c-0.2-0.1-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2c0.1,0.1,0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3c0.3,0.1,0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9s-0.3,0.4-0.5,0.6c-0.2,0.1-0.5,0.3-0.8,0.3s-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
c-0.5-0.1-0.9-0.3-1.3-0.6l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2s-0.4-0.1-0.7-0.2
c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.4-0.2-0.6-0.3c-0.2-0.1-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4c0.3-0.1,0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2c0.4,0.1,0.7,0.3,1,0.5L17.3,187.9z"/>
<path class="st1" d="M20.5,188.5v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L20.5,188.5z"/>
<path class="st1" d="M25.7,192.6l2.5-6.4h1l2.5,6.4h-1.3l-0.6-1.6h-2.2l-0.6,1.6H25.7z M28.8,187.6l-0.9,2.6h1.8L28.8,187.6z"/>
<path class="st1" d="M32.7,192.6v-6.4h2.7c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.3,0,0.5-0.1,0.8c-0.1,0.3-0.2,0.5-0.4,0.7c-0.2,0.2-0.4,0.4-0.6,0.5c-0.2,0.1-0.5,0.2-0.8,0.2h-1.5v2.1H32.7z M33.9,189.4h1.4
c0.2,0,0.4-0.1,0.6-0.3c0.2-0.2,0.2-0.4,0.2-0.8c0-0.2,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
c-0.1,0-0.2-0.1-0.3-0.1h-1.4V189.4z"/>
</g>
<path class="st3" d="M33.2,14.1H32v-0.7c0-1.6-2-2.3-3.6-2.3h-7c-1.6,0-2.4,0.6-2.4,2.3v0.7h-3c-1.6,0-3,0.9-3,2.5v10.7
c0,1.6,1.3,2.7,3,2.7h17.2c1.6,0,3.8-1.6,3.8-3.2V16.6C37,15,34.8,14.1,33.2,14.1z M24.7,28.2c-3.8,0-6.8-3.1-6.8-6.8
c0-3.8,3.1-6.8,6.8-6.8c3.8,0,6.8,3.1,6.8,6.8C31.5,25.2,28.5,28.2,24.7,28.2z"/>
<g>
<path class="st3" d="M17.3,38c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2c-0.2-0.1-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2c0.1,0.1,0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3c0.3,0.1,0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9s-0.3,0.4-0.5,0.6c-0.2,0.1-0.5,0.3-0.8,0.3s-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
c-0.5-0.1-0.9-0.3-1.3-0.6l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2S15.4,40,15.1,40
c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.4-0.2-0.6-0.3c-0.2-0.1-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4c0.3-0.1,0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2c0.4,0.1,0.7,0.3,1,0.5L17.3,38z"/>
<path class="st3" d="M20.5,38.6v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L20.5,38.6z"/>
<path class="st3" d="M25.7,42.7l2.5-6.4h1l2.5,6.4h-1.3l-0.6-1.6h-2.2l-0.6,1.6H25.7z M28.8,37.6l-0.9,2.6h1.8L28.8,37.6z"/>
<path class="st3" d="M32.7,42.7v-6.4h2.7c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
c0,0.3,0,0.5-0.1,0.8c-0.1,0.3-0.2,0.5-0.4,0.7c-0.2,0.2-0.4,0.4-0.6,0.5c-0.2,0.1-0.5,0.2-0.8,0.2h-1.5v2.1H32.7z M33.9,39.5h1.4
c0.2,0,0.4-0.1,0.6-0.3c0.2-0.2,0.2-0.4,0.2-0.8c0-0.2,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
c-0.1,0-0.2-0.1-0.3-0.1h-1.4V39.5z"/>
</g>
<circle class="st1" cx="24.7" cy="121.2" r="4"/>
<circle class="st1" cx="24.7" cy="171.3" r="4"/>
</svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

View file

@ -0,0 +1,48 @@
<html>
<head>
<title>Share</title>
<link rel="stylesheet" type="text/css" href="css/edit-style.css">
<link rel="stylesheet" type="text/css" href="css/SnapshotReview.css">
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/SnapshotReview.js"></script>
</head>
<body>
<div class="snapshot-container">
<div class="snapshot-column-left">
<div class="snapsection">
<label class="title">Snapshot successfully saved!</label>
</div>
<hr />
<div class="snapsection">
<div id="sharing">
<div class="prompt">Would you like to share your pic in the Snapshots feed?</div>
<div class="button">
<span class="compound-button">
<input type="button" class="blue" id="share" value="Share in Feed" onclick="shareSelected()"/>
<span class="glyph"></span>
</span>
</div>
</div>
<div class="button">
<input type="button" class="black" id="close" value="Don't Share" onclick="doNotShare()"/>
</div>
</div>
<hr />
<div class="snapsection">
<span class="setting">
<input type="button" class="glyph naked" id="snapshotSettings" value="@" onclick="snapshotSettings()" />
<label for="snapshotSettings">Snapshot settings</label>
</span>
<span class="setting checkbox">
<input id="openFeed" type="checkbox" checked/>
<label for="openFeed">Open feed after</label>
</span>
</div>
</div>
<div id="snapshot-images" class="snapshot-column-right"/>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,131 @@
/*
// SnapshotReview.css
//
// Created by Howard Stearns for David Rowe 8/22/2016.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
*/
.snapshot-container {
width: 100%;
padding-top: 3px;
}
.snapshot-column-left {
width: 320px;
position: absolute;
padding-top: 8px;
}
.snapshot-column-right {
margin-left: 342px;
}
.snapshot-column-right > div > img {
width: 100%;
}
@media (max-width: 768px) {
.snapshot-column-left {
position: initial;
width: 100%;
}
.snapshot-column-right {
margin-left: 0;
width: 100%;
}
.snapshot-column-right > div > img {
margin-top: 18px !important;
}
}
.snapshot-column-right > div {
position: relative;
padding: 2px;
}
.snapshot-column-right > div > img {
border: 2px solid #575757;
margin: -2px;
}
hr {
padding-left: 0;
padding-right: 0;
margin: 21px 0;
}
.snapsection {
text-align: center;
}
.title {
text-transform: uppercase;
font-size: 12px;
}
.prompt {
font-family: Raleway-SemiBold;
font-size: 14px;
}
div.button {
padding-top: 21px;
}
.compound-button {
position: relative;
height: auto;
}
.compound-button input {
padding-left: 40px;
}
.compound-button .glyph {
display: inline-block;
position: absolute;
left: 12px;
top: 16px;
width: 23px;
height: 23px;
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgaGVpZ2h0PSI0MCIKICAgd2lkdGg9IjQwIgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgdmlld0JveD0iMCAwIDQwIDQwIgogICB5PSIwcHgiCiAgIHg9IjBweCIKICAgdmVyc2lvbj0iMS4xIj48bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGEzNCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48ZGM6dGl0bGU+PC9kYzp0aXRsZT48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczMyIiAvPjxzdHlsZQogICAgIGlkPSJzdHlsZTQiCiAgICAgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiM0MTQwNDI7fQoJLnN0MXtmaWxsOiNDQ0NDQ0M7fQoJLnN0MntmaWxsOiMxMzk4QkI7fQoJLnN0M3tmaWxsOiMzMUQ4RkY7fQo8L3N0eWxlPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMSI+PGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJjaXJjbGUxMyIKICAgICAgIHI9IjQuNDQwMDAwMSIKICAgICAgIGN5PSIxMjYuMTciCiAgICAgICBjeD0iMjAuNTQwMDAxIgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTUiCiAgICAgICBkPSJtIDI4Ljg3LDEzOS4yNiBjIDAuMDEsLTAuMDEgMC4wMiwtMC4wMiAwLjAzLC0wLjAzIGwgMCwtMS44NiBjIDAsLTIuNjggLTIuMzMsLTQuNzcgLTUsLTQuNzcgbCAtNi40MiwwIGMgLTIuNjgsMCAtNC44NSwyLjA5IC00Ljg1LDQuNzcgbCAwLDEuODggMTYuMjQsMCB6IgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTciCiAgICAgICBkPSJtIDM4LjE3LDEyMy40MiBjIDAsLTMuOTcgLTMuMjIsLTcuMTkgLTcuMTksLTcuMTkgbCAtMjAuMzEsMCBjIC0zLjk3LDAgLTcuMTksMy4yMiAtNy4xOSw3LjE5IGwgMCwxNC4xOCBjIDAsMy45NyAzLjIyLDcuMTkgNy4xOSw3LjE5IGwgMjAuMzEsMCBjIDMuOTcsMCA3LjE5LC0zLjIyIDcuMTksLTcuMTkgbCAwLC0xNC4xOCB6IG0gLTEuNzgsMTQuMjcgYyAwLDMuMDMgLTIuNDYsNS40OSAtNS40OSw1LjQ5IGwgLTIwLjMyLDAgYyAtMy4wMywwIC01LjQ5LC0yLjQ2IC01LjQ5LC01LjQ5IGwgMCwtMTQuMTkgYyAwLC0zLjAzIDIuNDYsLTUuNDkgNS40OSwtNS40OSBsIDIwLjMzLDAgYyAzLjAzLDAgNS40OSwyLjQ2IDUuNDksNS40OSBsIDAsMTQuMTkgeiIKICAgICAgIGNsYXNzPSJzdDEiIC8+PC9nPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMiIgLz48L3N2Zz4=);
background-repeat: no-repeat;
background-size: 23px 23px;
}
.setting {
display: inline-table;
height: 28px;
}
.setting label {
display: table-cell;
vertical-align: middle;
font-family: Raleway-SemiBold;
font-size: 14px;
}
.setting + .setting {
margin-left: 18px;
}
input[type=button].naked {
font-size: 40px;
line-height: 40px;
width: 30px;
padding: 0;
margin: 0 0 -6px 0;
position: relative;
top: -6px;
left: -8px;
background: none;
}
input[type=button].naked:hover {
color: #00b4ef;
background: none;
}

View file

@ -0,0 +1,77 @@
"use strict";
//
// SnapshotReview.js
// scripts/system/html/js/
//
// Created by Howard Stearns 8/22/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var paths = [], idCounter = 0, useCheckboxes;
function addImage(data) {
var div = document.createElement("DIV"),
input = document.createElement("INPUT"),
label = document.createElement("LABEL"),
img = document.createElement("IMG"),
id = "p" + idCounter++;
function toggle() { data.share = input.checked; }
img.src = data.localPath;
div.appendChild(img);
data.share = true;
if (useCheckboxes) { // I'd rather use css, but the included stylesheet is quite particular.
// Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state.
label.setAttribute('for', id); // cannot do label.for =
input.id = id;
input.type = "checkbox";
input.checked = true;
input.addEventListener('change', toggle);
div.class = "property checkbox";
div.appendChild(input);
div.appendChild(label);
}
document.getElementById("snapshot-images").appendChild(div);
paths.push(data);
}
function handleShareButtons(shareMsg) {
var openFeed = document.getElementById('openFeed');
openFeed.checked = shareMsg.openFeedAfterShare;
openFeed.onchange = function () { EventBridge.emitWebEvent(openFeed.checked ? 'setOpenFeedTrue' : 'setOpenFeedFalse'); };
if (!shareMsg.canShare) {
// this means you may or may not be logged in, but can't share
// because you are not in a public place.
document.getElementById("sharing").innerHTML = "<p class='prompt'>Snapshots can be shared when they're taken in shareable places.";
}
}
window.onload = function () {
// Something like the following will allow testing in a browser.
//addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'});
//addImage({localPath: 'http://lorempixel.com/1512/1680'});
openEventBridge(function () {
// Set up a handler for receiving the data, and tell the .js we are ready to receive it.
EventBridge.scriptEventReceived.connect(function (message) {
// last element of list contains a bool for whether or not we can share stuff
var shareMsg = message.pop();
handleShareButtons(shareMsg);
// rest are image paths which we add
useCheckboxes = message.length > 1;
message.forEach(addImage);
});
EventBridge.emitWebEvent('ready');
});
};
// beware of bug: Cannot send objects at top level. (Nested in arrays is fine.)
function shareSelected() {
EventBridge.emitWebEvent(paths);
};
function doNotShare() {
EventBridge.emitWebEvent([]);
};
function snapshotSettings() {
EventBridge.emitWebEvent("openSettings");
};

View file

@ -527,12 +527,14 @@ function onDomainConnectionRefused(reason) {
createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED);
}
function onSnapshotTaken(path) {
var imageProperties = {
path: Script.resolvePath("file:///" + path),
aspectRatio: Window.innerWidth / Window.innerHeight
function onSnapshotTaken(path, notify) {
if (notify) {
var imageProperties = {
path: Script.resolvePath("file:///" + path),
aspectRatio: Window.innerWidth / Window.innerHeight
}
createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties);
}
createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties);
}
// handles mouse clicks on buttons

154
scripts/system/snapshot.js Normal file
View file

@ -0,0 +1,154 @@
//
// snapshot.js
//
// Created by David Kelly on 1 August 2016
// Copyright 2016 High Fidelity, Inc
//
// Distributed under the Apache License, Version 2.0
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var SNAPSHOT_DELAY = 500; // 500ms
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var resetOverlays;
var reticleVisible;
var button = toolBar.addButton({
objectName: "snapshot",
imageURL: Script.resolvePath("assets/images/tools/snap.svg"),
visible: true,
buttonState: 1,
defaultState: 1,
hoverState: 2,
alpha: 0.9,
});
function shouldOpenFeedAfterShare() {
var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false"
return persisted && (persisted !== 'false');
}
function showFeedWindow() {
DialogsManager.showFeed();
}
var outstanding;
function confirmShare(data) {
var dialog = new OverlayWebWindow('Snapshot Review', Script.resolvePath("html/SnapshotReview.html"), 800, 320);
function onMessage(message) {
// Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following:
// 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.)
// 2. Although we currently use a single image, we would like to take snapshot, a selfie, a 360 etc. all at the
// same time, show the user all of them, and have the user deselect any that they do not want to share.
// So we'll ultimately be receiving a set of objects, perhaps with different post processing for each.
var isLoggedIn;
var needsLogin = false;
switch (message) {
case 'ready':
dialog.emitScriptEvent(data); // Send it.
outstanding = 0;
break;
case 'openSettings':
Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
break;
case 'setOpenFeedFalse':
Settings.setValue('openFeedAfterShare', false)
break;
case 'setOpenFeedTrue':
Settings.setValue('openFeedAfterShare', true)
break;
default:
dialog.webEventReceived.disconnect(onMessage);
dialog.close();
isLoggedIn = Account.isLoggedIn();
message.forEach(function (submessage) {
if (submessage.share && !isLoggedIn) {
needsLogin = true;
submessage.share = false;
}
if (submessage.share) {
print('sharing', submessage.localPath);
outstanding++;
Window.shareSnapshot(submessage.localPath);
} else {
print('not sharing', submessage.localPath);
}
});
if (!outstanding && shouldOpenFeedAfterShare()) {
showFeedWindow();
}
if (needsLogin) { // after the possible feed, so that the login is on top
Account.checkAndSignalForAccessToken();
}
}
}
dialog.webEventReceived.connect(onMessage);
dialog.raise();
}
function snapshotShared(errorMessage) {
if (!errorMessage) {
print('snapshot uploaded and shared');
} else {
print(errorMessage);
}
if ((--outstanding <= 0) && shouldOpenFeedAfterShare()) {
showFeedWindow();
}
}
function onClicked() {
// update button states
resetOverlays = Menu.isOptionChecked("Overlays");
reticleVisible = Reticle.visible;
Reticle.visible = false;
Window.snapshotTaken.connect(resetButtons);
button.writeProperty("buttonState", 0);
button.writeProperty("defaultState", 0);
button.writeProperty("hoverState", 2);
// hide overlays if they are on
if (resetOverlays) {
Menu.setIsOptionChecked("Overlays", false);
}
// hide hud
toolBar.writeProperty("visible", false);
// take snapshot (with no notification)
Script.setTimeout(function () {
Window.takeSnapshot(false, 1.91);
}, SNAPSHOT_DELAY);
}
function resetButtons(path, notify) {
// show overlays if they were on
if (resetOverlays) {
Menu.setIsOptionChecked("Overlays", true);
}
// show hud
toolBar.writeProperty("visible", true);
Reticle.visible = reticleVisible;
// update button states
button.writeProperty("buttonState", 1);
button.writeProperty("defaultState", 1);
button.writeProperty("hoverState", 3);
Window.snapshotTaken.disconnect(resetButtons);
// last element in data array tells dialog whether we can share or not
confirmShare([
{ localPath: path },
{
canShare: !!location.placename,
openFeedAfterShare: shouldOpenFeedAfterShare()
}
]);
}
button.clicked.connect(onClicked);
Window.snapshotShared.connect(snapshotShared);
Script.scriptEnding.connect(function () {
toolBar.removeButton("snapshot");
button.clicked.disconnect(onClicked);
Window.snapshotShared.disconnect(snapshotShared);
});