Skip to content

Commit 96c71d1

Browse files
authored
Merge pull request #1836 from pierotofy/snapshot
Add snapshot plugin
2 parents 82b0ebe + 64c6122 commit 96c71d1

File tree

11 files changed

+180
-13
lines changed

11 files changed

+180
-13
lines changed

app/static/app/js/ModelView.jsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import ShareButton from './components/ShareButton';
88
import ImagePopup from './components/ImagePopup';
99
import Utils from './classes/Utils';
1010
import PropTypes from 'prop-types';
11+
import PluginsAPI from './classes/plugins/API';
12+
import update from 'immutability-helper';
1113
import * as THREE from 'THREE';
1214
import $ from 'jquery';
1315
import { _, interpolate } from './classes/gettext';
@@ -182,7 +184,8 @@ class ModelView extends React.Component {
182184
texModelLoadProgress: null,
183185
selectedCamera: null,
184186
modalOpen: false,
185-
cameraScale: CAMERA_SCALES[props.task.srs.units] || 1.0
187+
cameraScale: CAMERA_SCALES[props.task.srs.units] || 1.0,
188+
pluginActionButtons: []
186189
};
187190

188191
this.pointCloud = null;
@@ -515,6 +518,13 @@ class ModelView extends React.Component {
515518
viewer.renderer.domElement.addEventListener( 'mousemove', this.handleRenderMouseMove );
516519
viewer.renderer.domElement.addEventListener( 'touchstart', this.handleRenderTouchStart );
517520

521+
PluginsAPI.ModelView.triggerAddActionButton({
522+
viewer
523+
}, (button) => {
524+
this.setState(update(this.state, {
525+
pluginActionButtons: {$push: [button]}
526+
}));
527+
});
518528
}
519529

520530
handleUnitSystemChanged = () => {
@@ -851,6 +861,7 @@ class ModelView extends React.Component {
851861
buttonClass="btn-secondary"
852862
onModalOpen={() => this.setState({modalOpen: true})}
853863
onModalClose={() => this.setState({modalOpen: false})} />
864+
{this.state.pluginActionButtons.map((button, i) => <div key={i}>{button}</div>)}
854865
{(this.props.shareButtons && !this.props.public) ?
855866
<ShareButton
856867
ref={(ref) => { this.shareButton = ref; }}

app/static/app/js/classes/plugins/API.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EventEmitter } from 'fbemitter';
22
import ApiFactory from './ApiFactory';
33
import Map from './Map';
4+
import ModelView from './ModelView';
45
import Dashboard from './Dashboard';
56
import App from './App';
67
import SharePopup from './SharePopup';
@@ -30,6 +31,7 @@ if (!window.PluginsAPI){
3031

3132
window.PluginsAPI = {
3233
Map: factory.create(Map),
34+
ModelView: factory.create(ModelView),
3335
Dashboard: factory.create(Dashboard),
3436
App: factory.create(App),
3537
SharePopup: factory.create(SharePopup),
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Utils from '../Utils';
2+
import L from 'leaflet';
3+
4+
const { assert } = Utils;
5+
6+
const potreePreCheck = (options) => {
7+
assert(options.viewer !== undefined);
8+
};
9+
10+
export default {
11+
namespace: "ModelView",
12+
13+
endpoints: [
14+
["addActionButton", potreePreCheck]
15+
]
16+
};
17+

app/static/app/js/css/ModelView.scss

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@
1111
}
1212
}
1313

14+
.asset-download-buttons{
15+
button{
16+
padding: 5px 9px;
17+
i{
18+
left: 0;
19+
margin: 0;
20+
}
21+
}
22+
}
23+
1424
.container{
1525
background: rgb(79,79,79);
1626
background: -moz-radial-gradient(center, ellipse cover, rgba(79,79,79,1) 0%, rgba(22,22,22,1) 100%);
@@ -77,7 +87,7 @@
7787
}
7888
.asset-download-buttons{
7989
.dropdown-menu{
80-
left: -150px;
90+
left: -125px;
8191
}
8292
}
8393

coreplugins/snapshot/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .plugin import *

coreplugins/snapshot/manifest.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "Snapshot Button",
3+
"webodmMinVersion": "3.1.3",
4+
"description": "A plugin to add a button for taking a snapshot of the map or 3D model.",
5+
"version": "1.0.0",
6+
"author": "Piero Toffanin",
7+
"email": "pt@masseranolabs.com",
8+
"repository": "https://github.com/OpenDroneMap/WebODM",
9+
"tags": ["snapshot"],
10+
"homepage": "https://github.com/OpenDroneMap/WebODM",
11+
"experimental": true,
12+
"deprecated": false
13+
}

coreplugins/snapshot/plugin.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from app.plugins import PluginBase, Menu, MountPoint
2+
from django.shortcuts import render
3+
4+
class Plugin(PluginBase):
5+
def include_js_files(self):
6+
return ['main.js']
7+
8+
9+
10+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
(function(){
2+
var html2canvas;
3+
var title = "";
4+
5+
var takeScreenshot = function(el, ignore, onclone){
6+
return html2canvas(el, {
7+
allowTaint: true,
8+
useCORS: true,
9+
logging: false,
10+
onclone: onclone,
11+
removeContainer: false,
12+
ignoreElements: function(e){
13+
for (var i = 0; i < ignore.length; i++){
14+
if (e.classList.contains(ignore[i])) return true;
15+
}
16+
return false;
17+
}
18+
}).then(function(canvas) {
19+
var link = document.createElement('a');
20+
link.href = canvas.toDataURL();
21+
link.download = (title ? title.replace(/\s+/g, '-').replace(/[^0-9a-zA-Z\-_]+/g, '') + '-' : '') + 'snapshot.png';
22+
link.click();
23+
});
24+
}
25+
26+
var registerApiActionButton = function(apiActionButton, getTitle, getContainer, ignore = [], onclone = function(doc){ return doc; }){
27+
apiActionButton([
28+
'snapshot/node_modules/html2canvas/dist/html2canvas.min.js'
29+
], function(options, h2c){
30+
html2canvas = h2c;
31+
32+
var btnRef = null;
33+
title = getTitle(options);
34+
35+
var handleClick = function(){
36+
if (!btnRef) return;
37+
var icon = btnRef.querySelector("i");
38+
icon.className = "fa fa-circle-notch fa-spin";
39+
btnRef.disabled = true;
40+
41+
takeScreenshot(getContainer(options),
42+
ignore,
43+
onclone
44+
).then(function(){
45+
icon.className = "fa fa-camera";
46+
btnRef.disabled = false;
47+
});
48+
};
49+
50+
return React.createElement(
51+
"button",
52+
{
53+
type: "button",
54+
className: "btn btn-sm btn-secondary",
55+
title: "Take snapshot",
56+
style: {padding: "5px 9px"},
57+
ref: function(el){ btnRef = el; },
58+
onClick: handleClick
59+
},
60+
React.createElement("i", { className: "fa fa-camera" }, ""),
61+
""
62+
);
63+
});
64+
}
65+
66+
registerApiActionButton(PluginsAPI.Map.addActionButton, function(options){
67+
var mapTitle = options.map._container.parentElement.parentElement.parentElement.querySelector(".map-title");
68+
return mapTitle ? mapTitle.innerText.trim() : "";
69+
}, function(options){
70+
return options.map._container;
71+
},
72+
["leaflet-popup-pane", "leaflet-control-container"],
73+
function(doc){
74+
var overlay = doc.querySelector(".leaflet-overlay-pane .leaflet-zoom-animated");
75+
if (!overlay) return doc;
76+
77+
var match;
78+
if (overlay.style.transform.indexOf("translate3d") === 0) match = overlay.style.transform.match(/translate3d\((-?\d+)px,\s*(-?\d+)px/);
79+
else if (overlay.style.transform.indexOf("matrix") === 0) match = overlay.style.transform.match(/matrix\([\d\s,.-]+,\s*(-?\d+),\s*(-?\d+)\)/);
80+
81+
if (match) {
82+
overlay.style.top = parseInt(match[2]) + "px";
83+
overlay.style.left = parseInt(match[1]) + "px";
84+
overlay.style.transform = "";
85+
}
86+
87+
return doc;
88+
});
89+
registerApiActionButton(PluginsAPI.ModelView.addActionButton, function(options){
90+
var modelTitle = options.viewer.renderArea.parentElement.parentElement.parentElement.parentElement.querySelector(".model-title");
91+
return modelTitle ? modelTitle.innerText.trim() : "";
92+
}, function(options){
93+
return options.viewer.renderArea;
94+
},
95+
["quick_buttons_container"]);
96+
})();
97+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"html2canvas": "1.4.1"
4+
}
5+
}

docs/src/content/docs/plugin-development-guide.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,17 @@ PluginsAPI.hook([
163163

164164
```
165165

166-
| <div style="width:260px">Hook</div> | Triggered |
167-
| ------------------------------- | ------------------------------------------------------------------------------------------ |
168-
| `App.ready` | On DOM load |
169-
| `Dashboard.addTaskActionButton` | When buttons have been added to a task (next to View Map, View 3D Model, ..) |
170-
| `Dashboard.addNewTaskPanelItem` | When opening the panel after selecting images and GCPs |
171-
| `Dashboard.addNewTaskButton` | When buttons have been added to a project's panel (next to Select Images and GCP, Import)|
172-
| `Map.willAddControls` | When Leaflet controls are about to be added |
173-
| `Map.didAddControls` | When Leaflet controls have been added |
174-
| `Map.addActionButton` | When action buttons (bottom right of the screen) are about to be added |
175-
| `SharePopup.addLinkControl` | When rendering the Share dialog in Map View |
166+
| <div style="width:260px">Hook</div> | Triggered |
167+
| ----------------------------------- | ----------------------------------------------------------------------------------------- |
168+
| `App.ready` | On DOM load |
169+
| `Dashboard.addTaskActionButton` | When buttons have been added to a task (next to View Map, View 3D Model, ..) |
170+
| `Dashboard.addNewTaskPanelItem` | When opening the panel after selecting images and GCPs |
171+
| `Dashboard.addNewTaskButton` | When buttons have been added to a project's panel (next to Select Images and GCP, Import) |
172+
| `Map.willAddControls` | When Leaflet controls are about to be added |
173+
| `Map.didAddControls` | When Leaflet controls have been added |
174+
| `Map.addActionButton` | When action buttons (bottom right of the screen) are about to be added |
175+
| `ModelView.addActionButton` | When action buttons (bottom right of the screen) are about to be added (in 3D Model) |
176+
| `SharePopup.addLinkControl` | When rendering the Share dialog in Map View |
176177

177178
## Client Side Callbacks
178179

0 commit comments

Comments
 (0)