LCOV - code coverage report
Current view: top level - www/src/components - CameraControl.vue.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 8 73 11.0 %
Date: 2024-07-30 12:54:47 Functions: 3 18 16.7 %

          Line data    Source code
       1             : // @ts-check
       2             : 
       3             : import Vue from "vue";
       4             : import CameraCard from "./CameraCard.vue";
       5             : import Card from "../Card.vue";
       6             : import { debounce, forEach, throttle } from "lodash";
       7             : import { mapGetters } from "vuex";
       8             : import { sources } from "../store";
       9             : import { DateTime } from "luxon";
      10             : import d from "debug";
      11             : 
      12           1 : const debug = d("cam");
      13             : 
      14             : /**
      15             :  * @typedef {import('@cern/base-vue').BaseToggle} BaseToggle
      16             :  * @typedef {{
      17             :  *  cam: V.Instance<typeof CameraCard>,
      18             :  *  card: V.Instance<typeof Card>,
      19             :  *  focus: HTMLInputElement,
      20             :  *  iris: HTMLInputElement,
      21             :  *  autofocus: V.Instance<BaseToggle>,
      22             :  *  autoiris: V.Instance<BaseToggle>
      23             :  * }} Refs
      24             :  * @typedef {V.Instance<typeof component, V.ExtVue<any, Refs>>} Instance
      25             :  */
      26           1 : const component = /** @type {V.Constructor<any, Refs>} */(Vue).extend({
      27             :   name: "CameraControl",
      28             :   components: { CameraCard, Card },
      29             :   props: {
      30             :     group: { type: String, default: "" },
      31             :     camera: { type: String, default: "" },
      32             :     description: { type: String, default: "" },
      33             :     ptz: { type: Boolean, default: false }
      34             :   },
      35             :   /**
      36             :    * @return {{
      37             :    *   selected: null|CamExpress.CameraInfo, refreshing: boolean, advanced: boolean,
      38             :    *   position: any , limits: any, paused: boolean, screenshot: string,
      39             :    *   screenshotName: string, showControls: boolean, isFocused: boolean
      40             :    *  }}
      41             :    */
      42             :   data() {
      43          26 :     return { selected: null, refreshing: false, advanced: false, paused: false,
      44             :       position: null, limits: null, screenshot: "", screenshotName: "",
      45             :       showControls: false, isFocused: false
      46             :     };
      47             :   },
      48             :   computed: {
      49             :     .../** @type {{isOperator(): boolean, isExpert(): boolean}} */(mapGetters([
      50             :       "isOperator", "isExpert"
      51             :     ])),
      52             :     /**
      53             :      * @this {Instance}
      54             :      * @return {boolean}
      55             :      */
      56           0 :     canControl() { return this.isOperator || this.isExpert; }
      57             :   },
      58             :   /** @this {Instance} */
      59             :   mounted() {
      60          26 :     this.wheelMove = debounce(this.move, 300);
      61          26 :     this.move = throttle(this.move, 250);
      62          26 :     this.refreshAdvanced();
      63             : 
      64          26 :     this.showControls = this.$refs?.cam?.connected ?? false;
      65             :   },
      66             :   methods: {
      67             :     /** @param {any} params */
      68             :     async move(params, absolute = true) {
      69           0 :       if (!this.canControl) { return; }
      70           0 :       if (this.paused) { return; }
      71           0 :       debug("ptz move request", params);
      72           0 :       await sources.cam.movePTZ(this.group, this.camera, params, absolute);
      73             :     },
      74             :     /** @param {MouseEvent} event */
      75             :     async moveToPointer(event) {
      76           0 :       if (!this.canControl) { return; }
      77           0 :       if (this.paused ||
      78             :           !this.$refs.cam?.connected ||
      79             :           !this.isFocused || !this.$refs?.card?.isFocused ||
      80             :           !this.ptz) {
      81           0 :         return;
      82             :       }
      83             : 
      84           0 :       event?.preventDefault();
      85           0 :       event?.stopImmediatePropagation();
      86           0 :       const rect = /** @type {Element} */ (event.target)?.getBoundingClientRect?.();
      87           0 :       await sources.cam.movePTZ(this.group, this.camera, {
      88             :         imagewidth: Math.trunc(rect.width),
      89             :         imageheight: Math.trunc(rect.height),
      90             :         center: `${Math.trunc(event.clientX - rect.x)},${Math.trunc(event.clientY - rect.y)}`
      91             :       })
      92             :       .then(() => {
      93           0 :         if (this.advanced) {
      94           0 :           this.refreshAdvanced();
      95             :         }
      96             :       });
      97             :     },
      98             :     onMouseDown() {
      99             :       // 'click' event occurs always after 'focusin' one: let's check if the
     100             :       // card is already focused before a complete mouse click (down + up)
     101           0 :       this.isFocused = this.$refs?.card?.isFocused ?? false;
     102             :     },
     103             :     /**
     104             :      * @this {Instance}
     105             :      * @param {WheelEvent} event
     106             :      */
     107             :     onWheel(event) { /* eslint-disable-line complexity */
     108           0 :       if (!this.canControl) { return; }
     109           0 :       if (this.paused || !this.$refs.cam?.connected ||
     110           0 :         !this.$refs?.card?.isFocused || !this.ptz) { return; }
     111             : 
     112           0 :       event?.preventDefault();
     113           0 :       event?.stopImmediatePropagation();
     114             : 
     115           0 :       /** @type {number} */let zoom = (this.position?.zoom ?? 0);
     116           0 :       /** @type {number} */const zoomMin = (this.limits?.MinZoom ?? 0);
     117           0 :       /** @type {number} */const zoomMax = (this.limits?.MaxZoom ?? 9999);
     118           0 :       /** @type {number} */let delta = Math.abs(zoomMin - zoomMax);
     119           0 :       delta /= (event.ctrlKey ? 250 : 40);
     120             : 
     121           0 :       zoom += (event.deltaY > 0) ? -delta : delta;
     122           0 :       zoom = Math.max(zoomMin, Math.min(zoomMax, zoom));
     123             : 
     124           0 :       Object.assign(this.position ?? {}, { zoom });
     125           0 :       this.wheelMove?.({ zoom }, true);
     126             :     },
     127             :     /**
     128             :      * @this {Instance}
     129             :      * @param {any} position
     130             :      */
     131             :     updateAdvanced(position) {
     132           0 :       let input = this.$refs.autofocus;
     133           0 :       if (input) { input.editValue = position.autofocus; }
     134             : 
     135           0 :       input = this.$refs.autoiris;
     136           0 :       if (input) { input.editValue = position.autoiris; }
     137             : 
     138           0 :       forEach([ "pan", "tilt", "zoom", "brightness", "focus", "iris" ], (p) => {
     139           0 :         const input = this.$refs[p];
     140           0 :         if (input) { input.value = position[p]; }
     141             :       });
     142           0 :       this.$refs.focus?.setAttribute?.("disabled", position["autofocus"] ? "disabled" : "");
     143           0 :       this.$refs.iris?.setAttribute?.("disabled", position["autoiris"] ? "disabled" : "");
     144             :     },
     145             :     async refreshAdvanced() {
     146          26 :       if (this.refreshing || !this.ptz) { return; }
     147           0 :       this.refreshing = true;
     148           0 :       try {
     149           0 :         this.position = await sources.cam.getPTZ(this.group, this.camera);
     150           0 :         this.limits = await sources.cam.getPTZLimits(this.group, this.camera);
     151           0 :         this.updateAdvanced(this.position);
     152             :       }
     153             :       finally {
     154           0 :         this.refreshing = false;
     155             :       }
     156             :     },
     157             :     /** @this {Instance} */
     158             :     async toggleAdvanced() {
     159           0 :       this.advanced = !this.advanced;
     160           0 :       if (this.advanced) {
     161           0 :         this.refreshAdvanced();
     162             :       }
     163             :     },
     164             :     /**
     165             :      * @this {Instance}
     166             :      * @param {string} name
     167             :      */
     168             :     async toggleCtrl(name) {
     169           0 :       if (this.paused) { return; }
     170           0 :       await this.move({ [name]: !this.$refs[name]?.editValue });
     171           0 :       this.refreshAdvanced();
     172             :     },
     173             :     /** @this {Instance} */
     174             :     async togglePause() {
     175           0 :       const tmp = this.screenshot;
     176           0 :       this.paused = !this.paused;
     177             : 
     178           0 :       if (this.paused) {
     179           0 :         await sources.cam.getScreenshot(this.group, this.camera)
     180             :         .then(
     181             :           (ret) => {
     182           0 :             this.screenshot = URL.createObjectURL(ret);
     183             :           },
     184             :           () => {
     185           0 :             if (this.$refs.cam?.imgURL) {
     186           0 :               this.screenshot = this.$refs.cam.imgURL;
     187           0 :               this.$refs.cam.imgURL = "";
     188             :             }
     189             :           })
     190             :         .then(() => {
     191           0 :           if (!this.screenshot) { throw new Error("no image"); }
     192             : 
     193           0 :           const timestamp = DateTime.now()
     194             :           .set({ millisecond: 0 })
     195             :           .toISO({
     196             :             format: "extended",
     197             :             suppressMilliseconds: true,
     198             :             includeOffset: false
     199             :           });
     200           0 :           this.screenshotName = `${this.group}_${this.camera}_${timestamp}.jpeg`;
     201             :         })
     202           0 :         .catch((err) => console.warn('saving screenshot failed: ', err));
     203             :       }
     204             :       else {
     205           0 :         this.screenshot = "";
     206           0 :         this.screenshotName = "";
     207             :       }
     208           0 :       URL.revokeObjectURL(tmp);
     209             :     }
     210             :   }
     211             : });
     212             : 
     213             : export default component;

Generated by: LCOV version 1.16