mirror of https://github.com/bra1n/townsquare.git
add screenshot functionality
This commit is contained in:
parent
429d9845b1
commit
be44e977eb
|
@ -7,12 +7,4 @@ It is supposed to aid storytellers and allow them to quickly set up and capture
|
|||
|
||||
[You can try it online!](https://bra1n.github.io/townsquare)
|
||||
|
||||
**Todo:**
|
||||
- add night sheet data to roles.json
|
||||
- add night sheet view to Grimoire
|
||||
- add global reminder space
|
||||
- add LICENSE and finish README (shortcuts)
|
||||
- (maybe) switch to vectorized SVG token icons
|
||||
- allow using custom scripts
|
||||
|
||||
WORK IN PROGRESS
|
||||
|
|
34
src/App.vue
34
src/App.vue
|
@ -6,6 +6,7 @@
|
|||
:players="players"
|
||||
:roles="roles"
|
||||
:zoom="zoom"
|
||||
@screenshot="takeScreenshot"
|
||||
></TownSquare>
|
||||
|
||||
<Modal
|
||||
|
@ -34,7 +35,17 @@
|
|||
@close="isRoleModalOpen = false"
|
||||
></RoleSelectionModal>
|
||||
|
||||
<Screenshot
|
||||
ref="screenshot"
|
||||
@success="isScreenshotSuccess = true"
|
||||
></Screenshot>
|
||||
|
||||
<div class="controls">
|
||||
<font-awesome-icon
|
||||
icon="camera"
|
||||
@click="takeScreenshot()"
|
||||
v-bind:class="{ success: isScreenshotSuccess }"
|
||||
/>
|
||||
<font-awesome-icon icon="cogs" @click="isControlOpen = !isControlOpen" />
|
||||
<ul v-if="isControlOpen">
|
||||
<li @click="togglePublic">Toggle <em>G</em>rimoire</li>
|
||||
|
@ -74,9 +85,11 @@ import Modal from "./components/Modal";
|
|||
import RoleSelectionModal from "./components/RoleSelectionModal";
|
||||
import rolesJSON from "./roles";
|
||||
import editionJSON from "./editions";
|
||||
import Screenshot from "./components/Screenshot";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Screenshot,
|
||||
TownSquare,
|
||||
TownInfo,
|
||||
Modal,
|
||||
|
@ -89,6 +102,7 @@ export default {
|
|||
isControlOpen: false,
|
||||
isEditionModalOpen: false,
|
||||
isRoleModalOpen: false,
|
||||
isScreenshotSuccess: false,
|
||||
players: [],
|
||||
roles: this.getRolesByEdition(),
|
||||
edition: "tb",
|
||||
|
@ -96,6 +110,11 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
takeScreenshot(dimensions = {}) {
|
||||
this.isControlOpen = false;
|
||||
this.isScreenshotSuccess = false;
|
||||
this.$refs.screenshot.capture(dimensions);
|
||||
},
|
||||
togglePublic() {
|
||||
this.isPublic = !this.isPublic;
|
||||
this.isControlOpen = false;
|
||||
|
@ -266,6 +285,16 @@ ul {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
// success animation
|
||||
@keyframes greenToWhite {
|
||||
from {
|
||||
color: green;
|
||||
}
|
||||
to {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
// Controls
|
||||
.controls {
|
||||
position: absolute;
|
||||
|
@ -275,6 +304,11 @@ ul {
|
|||
padding: 10px;
|
||||
svg {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
&.success {
|
||||
animation: greenToWhite 1s normal forwards;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<li>
|
||||
<div
|
||||
ref="player"
|
||||
class="player"
|
||||
:class="{
|
||||
dead: player.hasDied,
|
||||
|
@ -13,7 +14,12 @@
|
|||
<Token :role="player.role" @set-role="setRole" />
|
||||
|
||||
<div class="name" @click="changeName">
|
||||
<span class="screenshot" @click.stop="takeScreenshot">
|
||||
<font-awesome-icon icon="camera" />
|
||||
</span>
|
||||
<span class="name">
|
||||
{{ player.name }}
|
||||
</span>
|
||||
<span class="remove" @click.stop="$emit('remove-player', player)">
|
||||
<font-awesome-icon icon="times-circle" />
|
||||
</span>
|
||||
|
@ -59,6 +65,10 @@ export default {
|
|||
return {};
|
||||
},
|
||||
methods: {
|
||||
takeScreenshot() {
|
||||
const { width, height, x, y } = this.$refs.player.getBoundingClientRect();
|
||||
this.$emit("screenshot", { width, height, x, y });
|
||||
},
|
||||
toggleStatus() {
|
||||
if (this.isPublic) {
|
||||
if (!this.player.hasDied) {
|
||||
|
@ -224,8 +234,11 @@ export default {
|
|||
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 1))
|
||||
drop-shadow(0 0 1px rgba(0, 0, 0, 1)) drop-shadow(0 0 1px rgba(0, 0, 0, 1));
|
||||
cursor: pointer;
|
||||
span {
|
||||
white-space: nowrap;
|
||||
span.screenshot,
|
||||
span.remove {
|
||||
display: none;
|
||||
margin: 0 10px;
|
||||
}
|
||||
&:hover {
|
||||
color: red;
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div id="screenshot">
|
||||
<video ref="video" autoplay></video>
|
||||
<canvas ref="canvas"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function() {
|
||||
return {
|
||||
stream: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async capture({ x, y, width, height }) {
|
||||
const canvas = this.$refs.canvas;
|
||||
const video = this.$refs.video;
|
||||
// start capturing
|
||||
if (!this.stream || !this.stream.active) {
|
||||
alert(
|
||||
"Please select to stream the current browser tab to get the appropriate screenshots"
|
||||
);
|
||||
try {
|
||||
this.stream = await navigator.mediaDevices.getDisplayMedia({
|
||||
video: {
|
||||
// frameRate: 5,
|
||||
cursor: "never"
|
||||
},
|
||||
audio: false
|
||||
});
|
||||
} catch (err) {
|
||||
this.$emit("error", err);
|
||||
}
|
||||
}
|
||||
// get screenshot
|
||||
if (this.stream && this.stream.active) {
|
||||
video.srcObject = this.stream;
|
||||
video.play();
|
||||
setTimeout(() => {
|
||||
const context = canvas.getContext("2d");
|
||||
canvas.setAttribute("width", width || video.videoWidth);
|
||||
canvas.setAttribute("height", height || video.videoHeight);
|
||||
context.drawImage(
|
||||
video,
|
||||
x || 0,
|
||||
y || 0,
|
||||
width || video.videoWidth,
|
||||
height || video.videoHeight,
|
||||
0,
|
||||
0,
|
||||
width || video.videoWidth,
|
||||
height || video.videoHeight
|
||||
);
|
||||
canvas.toBlob(blob => {
|
||||
try {
|
||||
// eslint-disable-next-line no-undef
|
||||
const item = new ClipboardItem({ "image/png": blob });
|
||||
navigator.clipboard.write([item]);
|
||||
this.$emit("success");
|
||||
} catch (err) {
|
||||
this.$emit("error", err);
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -15,10 +15,12 @@
|
|||
@add-reminder="openReminderModal"
|
||||
@set-role="openRoleModal"
|
||||
@remove-player="removePlayer"
|
||||
@screenshot="$emit('screenshot', $event)"
|
||||
></Player>
|
||||
</ul>
|
||||
<div class="bluffs" v-if="players.length > 6">
|
||||
<div class="bluffs" v-if="players.length > 6" ref="bluffs">
|
||||
<h3>Demon bluffs</h3>
|
||||
<font-awesome-icon icon="camera" @click.stop="takeScreenshot" />
|
||||
<ul>
|
||||
<li @click="openRoleModal(bluffs[0])">
|
||||
<Token :role="bluffs[0].role"></Token>
|
||||
|
@ -105,6 +107,10 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
takeScreenshot() {
|
||||
const { width, height, x, y } = this.$refs.bluffs.getBoundingClientRect();
|
||||
this.$emit("screenshot", { width, height, x, y });
|
||||
},
|
||||
openReminderModal(player) {
|
||||
this.availableRoles = [];
|
||||
this.availableReminders = [];
|
||||
|
@ -189,8 +195,8 @@ export default {
|
|||
}
|
||||
|
||||
> * {
|
||||
margin-left: -100px;
|
||||
width: 200px;
|
||||
margin-left: -78px;
|
||||
width: 156px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,6 +262,15 @@ export default {
|
|||
transform: scale(1);
|
||||
opacity: 1;
|
||||
transition: all 200ms ease-in-out;
|
||||
> svg {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
h3 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ import {
|
|||
faTimesCircle,
|
||||
faCogs,
|
||||
faSearchMinus,
|
||||
faSearchPlus
|
||||
faSearchPlus,
|
||||
faCamera
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
|
||||
|
@ -23,7 +24,8 @@ library.add(
|
|||
faTimesCircle,
|
||||
faCogs,
|
||||
faSearchMinus,
|
||||
faSearchPlus
|
||||
faSearchPlus,
|
||||
faCamera
|
||||
);
|
||||
|
||||
Vue.component("font-awesome-icon", FontAwesomeIcon);
|
||||
|
|
Loading…
Reference in New Issue