diff --git a/app/api/tasks.py b/app/api/tasks.py index 1fc08880..617c1635 100644 --- a/app/api/tasks.py +++ b/app/api/tasks.py @@ -9,6 +9,7 @@ from django.db.models.functions import Cast from django.http import HttpResponse from wsgiref.util import FileWrapper from rest_framework import status, serializers, viewsets, filters, exceptions, permissions, parsers +from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.response import Response from rest_framework.decorators import detail_route from rest_framework.views import APIView @@ -36,7 +37,7 @@ class TaskSerializer(serializers.ModelSerializer): class Meta: model = models.Task exclude = ('processing_lock', 'console_output', 'orthophoto_extent', 'dsm_extent', 'dtm_extent', ) - read_only_fields = ('processing_time', 'status', 'last_error', 'created_at', 'pending_action', 'available_assets', 'public_uuid', ) + read_only_fields = ('processing_time', 'status', 'last_error', 'created_at', 'pending_action', 'available_assets', ) class TaskViewSet(viewsets.ViewSet): """ @@ -171,6 +172,7 @@ class TaskViewSet(viewsets.ViewSet): class TaskNestedView(APIView): queryset = models.Task.objects.all().defer('orthophoto_extent', 'dtm_extent', 'dsm_extent', 'console_output', ) + permission_classes = (IsAuthenticatedOrReadOnly, ) def get_and_check_task(self, request, pk, project_pk, annotate={}): try: diff --git a/app/models/task.py b/app/models/task.py index 67ac9d5c..ca84f06f 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -447,6 +447,17 @@ class Task(models.Model): } } + def get_model_display_params(self): + """ + Subset of a task fields used in the 3D model display view + """ + return { + 'id': str(self.id), + 'project': self.project.id, + 'available_assets': self.available_assets, + 'public': self.public + } + def generate_deferred_asset(self, archive, directory): """ :param archive: path of the destination .zip file (relative to /assets/ directory) diff --git a/app/static/app/css/main.scss b/app/static/app/css/main.scss index 78e3c02d..eb1db8c1 100644 --- a/app/static/app/css/main.scss +++ b/app/static/app/css/main.scss @@ -6,6 +6,37 @@ html, body, section.main, .content, #wrapper, #page-wrapper{ padding-bottom: 8px; } +#public-wrapper{ + margin-left: 12px; + margin-right: 12px; + position: relative; + top: -8px; +} + +#iframe{ + .map-view{ + height: 100%; + .map-type-selector{ + z-index: 1000; + position: absolute; + float: none; + width: 100%; + display: flex; + justify-content: center; + margin-top: 10px; + } + .switchModeButton{ + bottom: 22px; + } + .opacity-slider{ + bottom: 10px; + } + } + [data-mapview], .model-view{ + height: calc(100vh); + } +} + #navbar-top{ height: 50px; min-height: 50px; diff --git a/app/static/app/js/MapView.jsx b/app/static/app/js/MapView.jsx index c08f0405..56184ac4 100644 --- a/app/static/app/js/MapView.jsx +++ b/app/static/app/js/MapView.jsx @@ -8,13 +8,15 @@ class MapView extends React.Component { static defaultProps = { mapItems: [], selectedMapType: 'orthophoto', - title: "" + title: "", + public: false }; static propTypes = { mapItems: PropTypes.array.isRequired, // list of dictionaries where each dict is a {mapType: 'orthophoto', url: }, selectedMapType: PropTypes.oneOf(['orthophoto', 'dsm', 'dtm']), title: PropTypes.string, + public: PropTypes.bool }; constructor(props){ @@ -101,7 +103,8 @@ class MapView extends React.Component { tiles={this.state.tiles} showBackground={true} opacity={opacity} - mapType={this.state.selectedMapType} /> + mapType={this.state.selectedMapType} + public={this.props.public} />
Opacity:
diff --git a/app/static/app/js/ModelView.jsx b/app/static/app/js/ModelView.jsx index 7a99753e..744804fa 100644 --- a/app/static/app/js/ModelView.jsx +++ b/app/static/app/js/ModelView.jsx @@ -4,6 +4,7 @@ import ErrorMessage from './components/ErrorMessage'; import SwitchModeButton from './components/SwitchModeButton'; import AssetDownloadButtons from './components/AssetDownloadButtons'; import Standby from './components/Standby'; +import ShareButton from './components/ShareButton'; import PropTypes from 'prop-types'; import $ from 'jquery'; @@ -15,11 +16,13 @@ import Potree from './vendor/potree'; class ModelView extends React.Component { static defaultProps = { - task: null + task: null, + public: false }; static propTypes = { task: PropTypes.object.isRequired, // The object should contain two keys: {id: , project: } + public: PropTypes.bool // Is the view being displayed via a shared link? }; constructor(props){ @@ -35,6 +38,7 @@ class ModelView extends React.Component { this.modelReference = null; this.toggleTexturedModel = this.toggleTexturedModel.bind(this); + this.handleMouseDown = this.handleMouseDown.bind(this); } assetsPath(){ @@ -66,6 +70,11 @@ class ModelView extends React.Component { return 'odm_textured_model.mtl'; } + handleMouseDown(e){ + // Make sure the share popup closes + this.shareButton.hidePopup(); + } + componentDidMount() { let container = this.container; @@ -179,7 +188,6 @@ class ModelView extends React.Component { className="container" style={{height: "100%", width: "100%", position: "relative"}} onContextMenu={(e) => {e.preventDefault();}}> -
{ this.container = domNode; }}> @@ -190,7 +198,7 @@ class ModelView extends React.Component {
-
+
diff --git a/app/static/app/js/components/ClipboardInput.jsx b/app/static/app/js/components/ClipboardInput.jsx index bf1ed731..c9235ee8 100644 --- a/app/static/app/js/components/ClipboardInput.jsx +++ b/app/static/app/js/components/ClipboardInput.jsx @@ -33,7 +33,7 @@ class ClipboardInput extends React.Component{ onBlur={() => { this.setState({showCopied: false}); }} />
-
Copied to clipboard!
+
Copied to clipboard
); } diff --git a/app/static/app/js/components/Map.jsx b/app/static/app/js/components/Map.jsx index bb984e36..b9efc01a 100644 --- a/app/static/app/js/components/Map.jsx +++ b/app/static/app/js/components/Map.jsx @@ -24,7 +24,8 @@ class Map extends React.Component { minzoom: 0, showBackground: false, opacity: 100, - mapType: "orthophoto" + mapType: "orthophoto", + public: false }; static propTypes = { @@ -33,7 +34,8 @@ class Map extends React.Component { showBackground: PropTypes.bool, tiles: PropTypes.array.isRequired, opacity: PropTypes.number, - mapType: PropTypes.oneOf(['orthophoto', 'dsm', 'dtm']) + mapType: PropTypes.oneOf(['orthophoto', 'dsm', 'dtm']), + public: PropTypes.bool }; constructor(props) { @@ -276,15 +278,17 @@ class Map extends React.Component {
- {this.state.singleTask !== null ? + {(!this.props.public && this.state.singleTask !== null) ? { this.shareButton = ref; }} task={this.state.singleTask} + linksTarget="map" /> : ""} + type="mapToModel" + public={this.props.public} />
); diff --git a/app/static/app/js/components/ShareButton.jsx b/app/static/app/js/components/ShareButton.jsx index f4483eb7..8be78799 100644 --- a/app/static/app/js/components/ShareButton.jsx +++ b/app/static/app/js/components/ShareButton.jsx @@ -7,9 +7,12 @@ import $ from 'jquery'; class ShareButton extends React.Component { static defaultProps = { task: null, + popupPlacement: 'top' }; static propTypes = { - task: PropTypes.object.isRequired + task: PropTypes.object.isRequired, + linksTarget: PropTypes.oneOf(['map', '3d']).isRequired, + popupPlacement: PropTypes.string } constructor(props){ @@ -37,14 +40,17 @@ class ShareButton extends React.Component { } render() { - return ( -
{ e.stopPropagation(); }}> - {this.state.showPopup ? - - : ""} + placement={this.props.popupPlacement} + linksTarget={this.props.linksTarget} + />; + + return ( +
{ e.stopPropagation(); }}> + {this.props.popupPlacement === 'top' && this.state.showPopup ? + popup : ""} + {this.props.popupPlacement === 'bottom' && this.state.showPopup ? + popup : ""}
); } diff --git a/app/static/app/js/components/SharePopup.jsx b/app/static/app/js/components/SharePopup.jsx index 0868199a..fb384de7 100644 --- a/app/static/app/js/components/SharePopup.jsx +++ b/app/static/app/js/components/SharePopup.jsx @@ -8,9 +8,12 @@ import ClipboardInput from './ClipboardInput'; class SharePopup extends React.Component{ static propTypes = { task: PropTypes.object.isRequired, + linksTarget: PropTypes.oneOf(['map', '3d']).isRequired, + placement: PropTypes.string, taskChanged: PropTypes.func }; static defaultProps = { + placement: 'top', taskChanged: () => {} }; @@ -57,11 +60,12 @@ class SharePopup extends React.Component{ } render(){ - const shareLink = Utils.absoluteUrl(`/public/task/${this.state.task.id}/map/`); - const iframeUrl = Utils.absoluteUrl(`public/task/${this.state.task.id}/iframe/`); + const shareLink = Utils.absoluteUrl(`/public/task/${this.state.task.id}/${this.props.linksTarget}/`); + const iframeUrl = Utils.absoluteUrl(`public/task/${this.state.task.id}/iframe/${this.props.linksTarget}/`); const iframeCode = ``; - return (
+ return (
{ e.stopPropagation(); }} + className={"sharePopup popover in " + this.props.placement}>

Share This Task

@@ -77,8 +81,7 @@ class SharePopup extends React.Component{ type="checkbox" checked={this.state.task.public} onChange={() => {}} - /> - Enabled + /> Enabled
diff --git a/app/static/app/js/components/SwitchModeButton.jsx b/app/static/app/js/components/SwitchModeButton.jsx index eb8cb161..0d730fd1 100644 --- a/app/static/app/js/components/SwitchModeButton.jsx +++ b/app/static/app/js/components/SwitchModeButton.jsx @@ -5,12 +5,16 @@ import PropTypes from 'prop-types'; class SwitchModeButton extends React.Component { static defaultProps = { task: null, - type: "mapToModel" + type: "mapToModel", + public: false, + style: {} }; static propTypes = { task: PropTypes.object, // The object should contain two keys: {id: , project: } - type: PropTypes.string // Either "mapToModel" or "modelToMap" + type: PropTypes.string, // Either "mapToModel" or "modelToMap" + public: PropTypes.bool, // Whether to use public or private URLs + style: PropTypes.object }; constructor(props){ @@ -24,8 +28,13 @@ class SwitchModeButton extends React.Component { handleClick(){ if (this.props.task){ - const prefix = this.props.type === 'mapToModel' ? '3d' : 'map'; - location.href = `/${prefix}/project/${this.props.task.project}/task/${this.props.task.id}/`; + const target = this.props.type === 'mapToModel' ? '3d' : 'map'; + + let url = this.props.public ? + `../${target}/` + : `/${target}/project/${this.props.task.project}/task/${this.props.task.id}/`; + + location.href = url; } } @@ -40,6 +49,7 @@ class SwitchModeButton extends React.Component { render() { return (