Binding A Media Feed/ Stream to Video Element - Vue 3 Composition API - vue.js

I am trying to bind a camera feed to a video element using Vue 3, but I can not seem to even get a basic example working. Can someone tell me what I am missing?
import { ref, reactive } from 'vue'
let stream: any = reactive({})
let constraints = {
audio: false,
video: {
width: { min: 1024, ideal: 1280, max: 1920 },
height: { min: 576, ideal: 720, max: 1080 },
facingMode: 'environment'
}
}
stream = await navigator.mediaDevices.getUserMedia(constraints)
Then in my template I have tried both of the following ways to bind
<video :srcObject.prop="stream" width="300" height="200" autoplay></video>
<video :srcObject.prope.camel="stream" autoplay playsinline></video>

Your code overwrites stream with the result of getUserMedia(), which effectively removes the original reactive() instance from the component's context.
Solution
Make stream a ref instead of reactive, and set its value property to the result of getUserMedia().
Also, to ensure the user's resources are activated only when necessary, call getUserMedia() in the component's mounted hook, and stop the stream in the beforeUnmount hook.
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const stream = ref(null)
const constraints = {
audio: false,
video: {
width: { min: 1024, ideal: 1280, max: 1920 },
height: { min: 576, ideal: 720, max: 1080 },
facingMode: 'environment',
},
}
onMounted(async () => {
stream.value = await navigator.mediaDevices.getUserMedia(constraints)
})
onBeforeUnmount(() => {
stream.value.getTracks().forEach(track => track.stop())
})
</script>
<template>
<video :srcObject="stream" width="300" height="200" autoplay></video>
</template>
demo

Related

Using the vue-konva with nuxtjs fails with various error

I followed the documentation and Github I did the following steps:
install vue-konva and konva and canvas using npm install vue-konva konva --save and npm install canvas --save.
Created vuekonva.js under plugins folder with below content:
import Vue from 'vue'
import VueKonva from 'vue-konva'
Vue.use(VueKonva)
Added plugins: [ "~/plugins/vuekonva"], under nuxt.config.js
I tried adding under nuxt-config.js but still no luck:
build: {
standalone: true
},
Created a page under pages folder and added code from documenation:
<template>
<div>
<v-stage ref="stage" :config="stageSize">
<v-layer>
<v-text :config="{ text: 'Some text on canvas', fontSize: 15 }" />
<v-rect
:config="{
x: 20,
y: 50,
width: 100,
height: 100,
fill: 'red',
shadowBlur: 10,
}"
/>
<v-circle
:config="{
x: 200,
y: 100,
radius: 50,
fill: 'green',
}"
/>
<v-line
:config="{
x: 20,
y: 200,
points: [0, 0, 100, 0, 100, 100],
tension: 0.5,
closed: true,
stroke: 'black',
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 50, y: 50 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow'],
}"
/>
</v-layer>
<v-layer ref="dragLayer" />
</v-stage>
</div>
</template>
<script>
export default {
data () {
return {
stageSize: {
width,
height
}
}
},
mounted () {
if (process.browser) {
this.stageSize.width = window.innerWidth
this.stageSize.height = window.innerHeight
}
}
}
</script>
I get the error:
Must use import to load ES Module:
I tried without plugins and it is throwing the error:
vue.runtime.esm.js:620 [Vue warn]: Unknown custom element: <v-stage> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
Not understanding whats the issue please help.
According to Nuxt documentation some plugins export an ES6 module. I think this is the case for konva node module. I followed the steps you mentioned above. But in the nuxt.config.js file I configured the plugin as follow:
plugins: [
{ src: "~/plugins/vuekonva", mode: 'client' }
],
build: {
transpile: ['konva']
},
After that I replaced the code of your page with the code of konvajs as follows:
<template>
<v-stage :config="configKonva">
<v-layer>
<v-circle :config="configCircle"></v-circle>
</v-layer>
</v-stage>
</template>
<script>
export default {
data() {
return {
configKonva: {
width: 200,
height: 200
},
configCircle: {
x: 100,
y: 100,
radius: 70,
fill: "red",
stroke: "black",
strokeWidth: 4
}
};
}
};
</script>
That is working for me when I link to the page with nuxt-link. but if I refresh the page I get some errors that is maybe for SSR. I am not sure but if you read this documentation you maybe could solve the problem for SSR.

Vue Unit Test Resize Screen and Update DOM

I have a component that displays an image if the screen is desktop and it hides the image if the screen is for mobile devices:
<script>
export default {
name: 'MyComponentApp',
}
</script>
<template>
<div class="my-component">
<div class="my-component__image-container">
<img class="my-component__image-container--img" />
</div>
</div>
</template>
<style lang="scss" scoped>
.my-component {
&__image-container {
overflow: hidden;
width: 50%;
&--img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
#media (max-width: 600px) {
.my-component {
&__image-container {
&--img {
display: none;
}
}
}
}
</style>
When I try to do the unit test case and test if the image is hidden when the window.width is below 600px, it doesn't update the DOM and the image is still visible:
import MyComponentApp from './MyComponentApp.vue';
import { shallowMount } from '#vue/test-utils';
const factory = () => {
return shallowMount(MyComponentApp, {});
};
describe('DownloadApp.vue', () => {
let wrapper;
beforeEach(() => {
wrapper = factory();
});
describe('Check Items on Mobile Devices', () => {
it('Img on div.my-component__image-container shouldn\'t be displayed', async () => {
jest.spyOn(screen, 'height', 'get').mockReturnValue(500);
jest.spyOn(screen, 'width', 'get').mockReturnValue(500);
await wrapper.vm.$nextTick();
const image = wrapper.find('div.my-component__image-container > img');
expect(image.isVisible()).toBe(false);
});
});
});
However, the test fails:
DownloadApp.vue › Check Items on Mobile Devices › Img on div.my-component__image-container shouldn\'t be displayed
expect(received).toBe(expected) // Object.is equality
Expected: false
Received: true
Does anybody know how to update the DOM or make the test case realized that the screen width has changed and the image should be displayed?
CSS #media queries depend on the viewport size, which currently cannot be manipulated from Jest alone, and setting window.innerWidth in Jest won't have an effect in this case.
Alternatively, you could resize the viewport in Cypress tests. In a Vue CLI project, you could add the Cypress plugin by running this command from the root of your project:
vue add e2e-cypress
Then in <root>/tests/e2e/test.js, insert the following tests that use cy.viewport() to set the viewport size before checking the img element's visibility:
describe('My img component', () => {
it('should show image for wide viewport', () => {
cy.visit('/') // navigate to page where test component exists
cy.viewport(800, 600)
cy.get('.my-component__image-container--img').should('be.visible')
})
it('should hide image for narrow viewport', () => {
cy.visit('/') // navigate to page where test component exists
cy.viewport(500, 600)
cy.get('.my-component__image-container--img').should('not.be.visible')
})
})

Find multiple features in a layer under mouse position arcgis onlne

I want to see all the features in a graphicsLayer that match an x,y from a mouse. The hitTest() method works for the topmost one:
mapView.on("pointer-move", function(event) { // HANDLE HOVER
let screenPoint={ x: event.x, y: event.y };
mapView.hitTest(screenPoint).then(function(response) {
if (response.results.length) {
DO SOMETHING...
}
});
But, when I'm zoomed out, the points overlay into one. How can I know that there are others and get access to them?
Google Earth used to have a function that automatically displayed them in a circle. It would be great if arcGis has that, but I'm with doing it the hard way.
In the new version of the API, 4.x, what usually was a the GraphicLayer purpose (handling client side features) became part of FeatureLayer or other layers like GeoJSONLayer or CSVLayer.
Now, the recomendation is to use FeatureLayer,
It is generally preferred to construct a FeatureLayer with its source property when working with client-side graphics since the FeatureLayer has more capabilities than the GraphicsLayer, including rendering, querying, and labeling.
ArcGIS JavaScript API - GraphicLayer
In regard to visualization, your could use clustering on FeatureLayer.
Take a look at this example I made for you base on ArcGIS JavaScript Examples - Point clustering.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>FeatureLayer Cluster - 4.15</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<link
rel="stylesheet"
href="https://js.arcgis.com/4.15/esri/themes/light/main.css"
/>
<script src="https://js.arcgis.com/4.15/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/Graphic",
"esri/geometry/Point",
"esri/widgets/Legend",
"esri/widgets/Expand"
], function(
Map,
MapView,
FeatureLayer,
Graphic,
Point,
Legend,
Expand
) {
function getRandomNumber(min, ref) {
return Math.random() * ref + min;
}
function getGraphics() {
const graphics = [];
let location = null;
// generate random points features
let oid = 0;
for (let i = 0; i <= getRandomNumber(20, 50); i++) {
location = new Point({
latitude: getRandomNumber(10, 50),
longitude: -getRandomNumber(50, 50)
});
for (let j = 0; j <= getRandomNumber(0, 50); j++) {
graphics.push(
new Graphic({
geometry: location,
attributes: {
OBJECTID: oid,
name: `${i}-${j}`
}
})
);
oid++;
}
}
return graphics;
}
const graphics = getGraphics();
function popupTemplateContent(feature) {
const location = feature.graphic.geometry;
return `lat:${location.latitude.toFixed(2)} lon:${location.longitude.toFixed(2)}`;
}
const clusterConfig = {
type: "cluster",
clusterRadius: "100px",
popupTemplate: {
content: "{cluster_count} features."
}
};
function createLayer() {
return new FeatureLayer({
source: graphics,
objectIdField: "OBJECTID",
fields: [
{
name: "OBJECTID",
type: "oid"
},
{
name: "name",
type: "string"
}
],
featureReduction: clusterConfig,
popupTemplate: {
title: '{name}',
content: popupTemplateContent
},
renderer: {
type: "simple",
field: "mag",
symbol: {
type: "simple-marker",
size: 4,
color: "#fc3232",
outline: {
color: [50, 50, 50]
}
}
}
});
}
const layer = createLayer();
const view = new MapView({
map: new Map({
basemap: "gray-vector"
}),
container: "viewDiv",
zoom: 2,
center: [-75, 35]
});
view
.when()
.then(addLayerToView)
.catch(function(e) {
console.error("Creating FeatureLayer failed", e);
});
function addLayerToView() {
view.map.add(layer);
}
const legend = new Legend({
view: view,
container: "legendDiv"
});
const infoDiv = document.getElementById("infoDiv");
view.ui.add(
new Expand({
view: view,
content: infoDiv,
expandIconClass: "esri-icon-layer-list",
expanded: true
}),
"top-left"
);
const toggleButton = document.getElementById("cluster");
toggleButton.addEventListener("click", function() {
const fr = layer.featureReduction;
layer.featureReduction =
fr && fr.type === "cluster" ? null : clusterConfig;
toggleButton.innerText =
toggleButton.innerText === "Enable Clustering"
? "Disable Clustering"
: "Enable Clustering";
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="infoDiv" class="esri-widget">
<button id="cluster" class="esri-button">Disable Clustering</button>
<div id="legendDiv"></div>
</div>
</body>
</html>
In the example, when you select if clustering is on it will show how many features there are, and if clustering is off you will get all features.

Is it possible to print a chart with vue-chartjs?

I am using vue-chartjs to render graphs on a webapp. I know you can print charts if you are using the original library. However I have no idea on how to do it with the vue version of the library.
I have my charts variable on an external charts.js file
import {Bar, mixins } from 'vue-chartjs'
Chart.defaults.global
let chartOptions = Chart.defaults.global;
const { reactiveProp } = mixins
export default {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
let that = this;
that.chartOptions = {
scales: {
yAxes: [{
ticks: {
suggestedMin: 0,
fontFamily: "'Overpass_Mono', 'Monaco', monospace",
fontColor: "rgba(254, 255, 248, 0.5)"
},
gridLines: {
color: 'rgba(255, 80, 248, 0.08)',
zeroLineColor: 'rgb(168, 119, 181)',
zeroLineWidth: 2
},
}],
xAxes: [{
ticks: {
suggestedMin: 0,
fontColor: "rgb(168, 119, 181)"
},
gridLines: {
color: 'rgba(255, 80, 248, 0.08)',
zeroLineColor: 'transparent',
}
}],
},
legend: {
labels: {
fontColor: 'rgb(168, 119, 181)',
}
}
},
this.renderChart(this.chartData, that.chartOptions)
}
}
Then on my component template I have:
<template>
<div class="report">
<charts v-if="todaySelected"
:chart-id="'total_visits_chart_bar'"
:height="chartsHeight"
:options="chartOptions"
:chart-data="datacollection"
></charts>
<div v-if="todaySelected">
<button #click="printChart(charts)">Print chart</button>
</div>
</template>
<script>
import charts from './chart_0.js'
components: {
charts,
},
data() {
return{
datacollection: {"datasets":[{"label":"Entries Today","data":[15,15,15,0]},{"label":"Currently Inside","data":[2,2,2,0]}],"labels":[]}
}
}.
methods: {
printChart(charts) {
charts.print();
},
}
</script>
Any help would be appreciated.
The answer is: Yes, it is. Your print method in the components' script could be:
methods:{
printChart() {
var canvasEle = document.getElementById('total_visits_chart_bar');
var win = window.open('', 'Print', 'height=600,width=800');
win.document.write("<br><img src='" + canvasEle.toDataURL() + "' />");
setTimeout(function(){ //giving it 200 milliseconds time to load
win.document.close();
win.focus()
win.print();
win.location.reload()
}, 200);
},
}
You can also add this to your component's style:
#media print{
#page {
size: landscape
}
}
vue-chartjs is based on chart.js and not canvas.js, thus it does not have a "build-in" way of printing.
You have to do it with some custom logic and the native javascript printing functions.
You can however grab the canvas element inside your chart component and generate for example an image and then print that image.
It will get a bit tricky, because you only have access to the canvas inside your chart component. So you will need to maybe wait for an event or prop to trigger the toDataURL call and then emit the image to your parent component where you can print it. If you want to trigger the print in your parent component.
methods: {
print () {
// grab the canvas and generate an image
let image = this.$refs.canvas.toDataURL('image/png')
// Emits an event with the image
this.$emit('chart:print', image)
}
}
In your parent component:
<template>
<your-chart #chart:print="handlePrint"
<template/>
....
...
methods: {
handlePrint(image) {
const win = window.open('', 'Print', 'height=600, width=800')
win.document.write(`<br><img src='${image}' />`)
win.print()
win.close()
}
}
It seems like the library is based on chartjs not canvasjs https://www.chartjs.org/docs/latest/ you might want to look into how to print a window Quick Print HTML5 Canvas, and remember you have access to the canvas element where your graph is drawn:
methods: {
printChart() {
const canvasEle = this.$el.querySelector('canvas');
//now your chart image is on canvasEle
},
}
If you are not against using export to pdf format, you can implement this task using jsPDF library, for example:
<template>
<div class="report">
<charts v-if="todaySelected"
:chart-id="'total_visits_chart_bar'"
:height="chartsHeight"
:options="chartOptions"
:chart-data="datacollection"
></charts>
</div>
</template>
<script>
import jsPDF from 'jspdf'; //for PDF printing
methods: {
pdfThatThing : function(){
//Default export is a4 paper, portrait, using milimeters for units
let pdfName = 'test';
var doc = new jsPDF();
doc.text("Header", 20, 20); //at x,y at def.units 2cm
//chart element
let canvasEle = document.getElementById('total_visits_chart_bar');
let chartURL = canvasEle.toDataURL(); //transform path
//a4 page is 209mm, adds at 4cm top, 2cm left, for 15cm in size
doc.addImage(chartURL, 'PNG', 20, 40, 150, 150 )
doc.save(pdfName + '.pdf');
},
}
</script>
There is also option to auto show print dialog in pdf viewer:
doc.autoPrint({variant: 'non-conform'})

Does Vue-Konva support GLSL?

Currently I'm using Vue-Konva.js to help me build a 2d editor for panorama.
Does konva.js support GLSL code for apply image effect (ex: stereographic projection)?
Maybe like this format.
KonvaScens.vue
<template>
<v-stage :config="configKonva">
<v-layer>
<v-image :config="configImage"></v-image>
</v-layer>
</v-stage>
</template>
<script>
import {myVertexShader, myFragmentShader} from 'imageShader'
export default {
data () {
return {
testImg: new Image(100, 100),
configKonva: {
width: 500,
height: 500
}
}
},
computed: {
configImage () {
return {
x: 20,
y: 20,
image: this.testImg,
width: 500,
height: 500,
vertexShader: myVertexShader,
fragmentShader: myFragmentShader
}
}
},
mounted () {
this.testImg.src = "https://konvajs.github.io/assets/lion.png"
}
}
</script>
No, vue-konva doesn't support GLSL. GLSL can be used in webgl canvas context. But Konva uses only 2d context.