You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
7.4 KiB
248 lines
7.4 KiB
<template>
|
|
<!-- QUICK NAV -->
|
|
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
|
|
<ul class="flex text-2xl p-2 gap-2">
|
|
<!-- Home Feed -->
|
|
<li class="basis-1/5 rounded-md text-slate-500">
|
|
<router-link :to="{ name: 'home' }" class="block text-center py-3 px-1">
|
|
<fa icon="house-chimney" class="fa-fw"></fa>
|
|
</router-link>
|
|
</li>
|
|
<!-- Search -->
|
|
<li class="basis-1/5 rounded-md text-slate-500">
|
|
<router-link
|
|
:to="{ name: 'discover' }"
|
|
class="block text-center py-3 px-1"
|
|
>
|
|
<fa icon="magnifying-glass" class="fa-fw"></fa>
|
|
</router-link>
|
|
</li>
|
|
<!-- Projects -->
|
|
<li class="basis-1/5 rounded-md text-slate-500">
|
|
<router-link
|
|
:to="{ name: 'projects' }"
|
|
class="block text-center py-3 px-1"
|
|
>
|
|
<fa icon="folder-open" class="fa-fw"></fa>
|
|
</router-link>
|
|
</li>
|
|
<!-- Contacts -->
|
|
<li class="basis-1/5 rounded-md text-slate-500">
|
|
<router-link
|
|
:to="{ name: 'contacts' }"
|
|
class="block text-center py-3 px-1"
|
|
>
|
|
<fa icon="users" class="fa-fw"></fa>
|
|
</router-link>
|
|
</li>
|
|
<!-- Profile -->
|
|
<li class="basis-1/5 rounded-md bg-slate-400 text-white">
|
|
<router-link
|
|
:to="{ name: 'account' }"
|
|
class="block text-center py-3 px-1"
|
|
>
|
|
<fa icon="circle-user" class="fa-fw"></fa>
|
|
</router-link>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<!-- CONTENT -->
|
|
<section id="Content" class="p-6 pb-24">
|
|
<!-- Heading -->
|
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
|
Achievements & Statistics
|
|
</h1>
|
|
|
|
<div>
|
|
Here is a view of the activity you can see.
|
|
<ul class="list-disc list-inside">
|
|
<li>Each identity and claim has a unique position.</li>
|
|
<!-- eslint-disable prettier/prettier --><!-- If we format prettier then there is extra space at the start of the line. -->
|
|
<li>Each will show at their time of appearance relative to all others.</li>
|
|
<li>Note that the ones on the left and right edges are randomized
|
|
because not all their positional data is visible to you.
|
|
</li>
|
|
<!-- eslint-enable -->
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<div v-if="worldProperties.startTime">
|
|
<label>Time Range: </label>
|
|
{{ worldProperties.startTime }}
|
|
-
|
|
{{ worldProperties.endTime }}
|
|
</div>
|
|
<div v-if="worldProperties.animationDurationSeconds">
|
|
<label>Animation duration: </label>
|
|
{{ worldProperties.animationDurationSeconds }} seconds
|
|
</div>
|
|
</div>
|
|
|
|
<button class="float-right" @click="captureGraphics()">Screenshot</button>
|
|
|
|
<!-- Another place to play with the sizing is in Resizer.setSize -->
|
|
<div id="scene-container" class="h-screen"></div>
|
|
|
|
<div v-bind:class="computedAlertClassNames()">
|
|
<button
|
|
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
|
|
@click="onClickClose()"
|
|
>
|
|
<fa icon="xmark"></fa>
|
|
</button>
|
|
<h4 class="font-bold pr-5">{{ alertTitle }}</h4>
|
|
<p>{{ alertMessage }}</p>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { SVGRenderer } from "three/addons/renderers/SVGRenderer.js";
|
|
import { Component, Vue } from "vue-facing-decorator";
|
|
import { World } from "@/components/World/World.js";
|
|
|
|
interface WorldProperties {
|
|
startTime?: string;
|
|
endTime?: string;
|
|
}
|
|
|
|
@Component
|
|
export default class StatisticsView extends Vue {
|
|
world: World;
|
|
worldProperties: WorldProperties = {};
|
|
|
|
mounted() {
|
|
const container = document.querySelector("#scene-container");
|
|
const newWorld = new World(container, this);
|
|
newWorld.start();
|
|
this.world = newWorld;
|
|
}
|
|
|
|
public captureGraphics() {
|
|
/**
|
|
// from https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#examples
|
|
// Adds a blank image
|
|
const dataBlob = document
|
|
.querySelector("#scene-container")
|
|
.firstChild.toBlob((blob) => {
|
|
const newImg = document.createElement("img");
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
newImg.onload = () => {
|
|
// no longer need to read the blob so it's revoked
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
newImg.src = url;
|
|
document.body.appendChild(newImg);
|
|
});
|
|
**/
|
|
|
|
/**
|
|
// Yields a blank page with the iframe below
|
|
const dataUrl = document
|
|
.querySelector("#scene-container")
|
|
.firstChild.toDataURL("image/png");
|
|
**/
|
|
|
|
/**
|
|
// Yields a blank page with the iframe below
|
|
const dataUrl = this.world.renderer.domElement.toDataURL("image/png");
|
|
**/
|
|
|
|
/**
|
|
// Show the image in a new tab
|
|
const iframe = `
|
|
<iframe
|
|
src="${dataUrl}"
|
|
frameborder="0"
|
|
style="border:0; top:0px; left:0px; bottom:0px; right:0px; width:100%; height:100%;"
|
|
allowfullscreen>
|
|
</iframe>`;
|
|
const win = window.open();
|
|
win.document.open();
|
|
win.document.write(iframe);
|
|
win.document.close();
|
|
**/
|
|
|
|
// from https://stackoverflow.com/a/17407392/845494
|
|
// This yields a file with funny formatting.
|
|
//const image = const dataUrl.replace("image/png", "image/octet-stream");
|
|
|
|
/**
|
|
// Yields a blank image at the bottom of the page
|
|
// from https://discourse.threejs.org/t/save-screenshot-on-server/39900/3
|
|
const img = new Image();
|
|
img.src = this.world.renderer.domElement.toDataURL();
|
|
document.body.appendChild(img);
|
|
**/
|
|
|
|
/**
|
|
* This yields an SVG that only shows white and black highlights
|
|
// from https://stackoverflow.com/questions/27632621/exporting-from-three-js-scene-to-svg-or-other-vector-format
|
|
**/
|
|
const rendererSVG = new SVGRenderer();
|
|
rendererSVG.setSize(window.innerWidth, window.innerHeight);
|
|
rendererSVG.render(this.world.scene, this.world.camera);
|
|
//document.body.appendChild(rendererSVG.domElement);
|
|
ExportToSVG(rendererSVG, "test.svg");
|
|
}
|
|
|
|
public setWorldProperty(propertyName, propertyValue) {
|
|
this.worldProperties[propertyName] = propertyValue;
|
|
}
|
|
|
|
alertTitle = "";
|
|
alertMessage = "";
|
|
isAlertVisible = false;
|
|
|
|
public setAlert(title, message) {
|
|
this.alertTitle = title;
|
|
this.alertMessage = message;
|
|
this.isAlertVisible = true;
|
|
}
|
|
|
|
public onClickClose() {
|
|
this.isAlertVisible = false;
|
|
this.alertTitle = "";
|
|
this.alertMessage = "";
|
|
}
|
|
|
|
public computedAlertClassNames() {
|
|
return {
|
|
hidden: !this.isAlertVisible,
|
|
"dismissable-alert": true,
|
|
"bg-slate-100": true,
|
|
"p-5": true,
|
|
rounded: true,
|
|
"drop-shadow-lg": true,
|
|
fixed: true,
|
|
"top-3": true,
|
|
"inset-x-3": true,
|
|
"transition-transform": true,
|
|
"ease-in": true,
|
|
"duration-300": true,
|
|
};
|
|
}
|
|
}
|
|
|
|
function ExportToSVG(rendererSVG, filename) {
|
|
const XMLS = new XMLSerializer();
|
|
const svgfile = XMLS.serializeToString(rendererSVG.domElement);
|
|
const svgData = svgfile;
|
|
const preface = '<?xml version="1.0" standalone="no"?>\r\n';
|
|
const svgBlob = new Blob([preface, svgData], {
|
|
type: "image/svg+xml;charset=utf-8",
|
|
});
|
|
const svgUrl = URL.createObjectURL(svgBlob);
|
|
const downloadLink = document.createElement("a");
|
|
|
|
downloadLink.href = svgUrl;
|
|
downloadLink.download = filename;
|
|
document.body.appendChild(downloadLink);
|
|
downloadLink.click();
|
|
document.body.removeChild(downloadLink);
|
|
}
|
|
</script>
|
|
|