‘IncompleteSignature’ The request signature does not conform to platform standards

I need to help
I want to create callback lazada api for receive buyer message push in my market but i can’t create access_token because response from api.lazada is “IncompleteSignature” I try solution on another post in stackoverflow “toUpperCase” or “UTF-8” and doesn’t work

first :

import { buffer } from "micro";
import crypto from "crypto";

export const config = { api: { bodyParser: false } };

const APP_KEY = process.env.LAZADA_APP_KEY;
const APP_SECRET = process.env.LAZADA_APP_SECRET;
const REDIRECT_URI = process.env.LAZADA_REDIRECT_URI;
const OAUTH_HOST = "https://api.lazada.com";
console.log("App key:", APP_KEY);
console.log("App secret:", APP_SECRET);
console.log("Redirect URI:", REDIRECT_URI);

function verifySignature(rawBody, signature) {
  const expected = crypto
    .createHmac("sha256", APP_SECRET)
    .update(APP_KEY + rawBody)
    .digest("hex");
  return signature === expected;
}

Second :

async function handleOAuthCallback(req, res) {
  const { code } = req.query;
  if (!code) return res.status(400).send("Missing code");

  // สร้าง timestamp เป็น milliseconds
  const timestamp = Date.now().toString();

  const path = "/rest/auth/token/create";
  const params = {
    app_key: APP_KEY,
    code,
    grant_type: "authorization_code",
    redirect_uri: REDIRECT_URI,
    timestamp,
    sign_method: "sha256",
  };

  const sortedKeys = Object.keys(params).sort();
  const canonical = sortedKeys.map((k) => `${k}${params[k]}`).join("");
  console.log("Canonicalized:", canonical);

  const toSign = `GET${path}${canonical}`;
  console.log("String to sign:", toSign);

  const signature = crypto
    .createHmac("sha256", APP_SECRET)
    .update(Buffer.from(toSign, "utf8"))
    .digest("hex")
    .toUpperCase();
  console.log("Signature:", signature);

  const query = sortedKeys
    .map((k) => `${k}=${encodeURIComponent(params[k])}`)
    .join("&");
  const url = `${OAUTH_HOST}${path}?${query}&sign=${signature}`;
  console.log("OAuth URL:", url);

  console.log("=============LOG=============");
  console.log("Timestamp:", timestamp);
  console.log("Raw params:", params);
  console.log("Sorted keys:", sortedKeys);
  console.log("Query string:", query);
  console.log("=============================");
  const resp = await fetch(url);
  const json = await resp.json();
  console.log("Lazada response:", json);

  if (json.code !== "0") {
    return res.status(500).json({ error: json });
  }

  return res.status(200).json(json.data);
}

and Last :

export default async function handler(req, res) {
  if (req.method === "GET") {
    const { challenge, code } = req.query;

    // ตรวจสอบ webhook challenge (เฉพาะกรณีที่ใช้ webhook IM)
    if (challenge) return res.status(200).send(challenge);

    // จัดการ OAuth Callback
    if (code) return await handleOAuthCallback(req, res);
  }

  // POST สำหรับ webhook (IM/chat)
  if (req.method === "POST") {
    const raw = (await buffer(req)).toString();
    const sig = req.headers["x-lazada-signature"];

    if (!sig || !verifySignature(raw, sig)) {
      return res.status(401).send("Invalid signature");
    }

    const body = JSON.parse(raw);

    if (body.message_type === "chat_message") {
      console.log("Chat message:", body.data);
    }

    return res.status(200).json({ success: true });
  }

  res.setHeader("Allow", ["GET", "POST"]);
  res.status(405).end();
}

my code build on Nextjs

fabric:Prevent text flipping in the group

I’m trying to scale a group without the text inside it flipping. The code seems to work, but when I drag to scale the group, the text keeps flipping back and forth. It’s a bit of a hassle.Here’s my code for reference:

const canvas = new fabric.Canvas('c')

const points = [{ x: 3, y: 4, }, { x: 16, y: 3, }, { x: 30, y: 5, }, { x: 25, y: 55, }, { x: 19, y: 44, }, { x: 15, y: 30, }, { x: 15, y: 55, }, { x: 9, y: 55, }, { x: 6, y: 53, }, { x: -2, y: 55, }, { x: -4, y: 40, }, { x: 0, y: 20, },
]

const poly = new fabric.Polygon(points, {
  left: 200,
  top: 50,
  fill: 'yellow',
  strokeWidth: 1,
  stroke: 'grey',
  scaleX: 5,
  scaleY: 5,
  objectCaching: false,
  transparentCorners: false,
  cornerColor: 'blue',
})
const text = new fabric.Text('hello text', {
  left: 200,
  top: 200,
  fontSize: 20,
  fill: 'red',
  originX: 'center',
  originY: 'center',
  hasRotatingPoint: false,
  lockScalingFlip: true
})
const group = new fabric.Group([poly, text], {
  originX: 'center',
  originY: 'center',
  hasRotatingPoint: false
})
canvas.add(group)

canvas.on('object:scaling', function (e) {
  const obj = e.target

  if (obj === group) {
    let flipX = obj.flipX
    let flipY = obj.flipY
    obj.getObjects()[1].set({
      scaleX: flipX ? -1 : 1,
      scaleY: flipY ? -1 : 1,
    })
  }

})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.0/fabric.min.js"></script>
<body>
  <canvas id="c" width="900" height="900"></canvas>
</body>

Rendering single band in geoserver layer containing multiple bands

I am using Geoserver to serve multiband layer. This layer contains 12 bands one for each month (named Band1, Band2, Band3,…). I want to render data of only 1 band in my application. I am using google maps as base map. I cannot make different layers for this. I will have to use a single layer with multiple bands.
My code right now is:

'use client';

import React, { useRef, useCallback } from 'react';
import { GoogleMap, LoadScript } from '@react-google-maps/api';

// const libraries = ['places'];
const GEOSERVER_WMS_URL = 'http://192.168.3.157:8080/geoserver/poc/wms';

const containerStyle = {
  width: '100%',
  height: '100vh',
};

const center = {
  lat: 22.5,
  lng: 80,
};

const sldBody = `
<StyledLayerDescriptor version="1.0.0"
  xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
  xmlns="http://www.opengis.net/sld"
  xmlns:ogc="http://www.opengis.net/ogc"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <NamedLayer>
    <Name>poc:PM2.5_HI_2023_all</Name>
    <UserStyle>
      <FeatureTypeStyle>
        <Rule>
          <RasterSymbolizer>
            <Opacity>1.0</Opacity>
            <ChannelSelection>
              <GrayChannel>
                <SourceChannelName>Band1</SourceChannelName>
              </GrayChannel>
            </ChannelSelection>
          </RasterSymbolizer>
        </Rule>
      </FeatureTypeStyle>
    </UserStyle>
  </NamedLayer>
</StyledLayerDescriptor>
`;
const encodedSLD = encodeURIComponent(sldBody);

function tileToBounds(x: number, y: number, z: number): string {
  const TILE_SIZE = 256;
  const worldCoordRadius = (TILE_SIZE * Math.pow(2, z)) / (2 * Math.PI);

  // Convert tile coordinates to radians
  const lon1 = (x * TILE_SIZE) / worldCoordRadius - Math.PI;
  const lat1 = Math.atan(Math.sinh(Math.PI - ((y + 1) * TILE_SIZE) / worldCoordRadius));

  const lon2 = ((x + 1) * TILE_SIZE) / worldCoordRadius - Math.PI;
  const lat2 = Math.atan(Math.sinh(Math.PI - (y * TILE_SIZE) / worldCoordRadius));

  const radToDeg = 180 / Math.PI;
  const minLon = lon1 * radToDeg;
  const minLat = lat1 * radToDeg;
  const maxLon = lon2 * radToDeg;
  const maxLat = lat2 * radToDeg;

  const R_MAJOR = 6378137.0;

  const projectToMercator = (lat: number, lon: number): { x: number; y: number } => {
    const x = R_MAJOR * lon * (Math.PI / 180);
    const y = R_MAJOR * Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360));
    return { x, y };
  };

  const minXY = projectToMercator(minLat, minLon);
  const maxXY = projectToMercator(maxLat, maxLon);

  return `${minXY.x},${minXY.y},${maxXY.x},${maxXY.y}`;
}

const MapWithWmsOverlay = () => {
  const mapRef = useRef<google.maps.Map | null>(null);

  const onLoad = useCallback((mapInstance: google.maps.Map) => {
    mapRef.current = mapInstance;

    const wmsOverlay = new google.maps.ImageMapType({
      getTileUrl: (coord, zoom) => {
        console.log("TESTING", sldBody);
        
        const bounds = tileToBounds(coord.x, coord.y, zoom);
        const url = new URL(GEOSERVER_WMS_URL);
        url.searchParams.set('SERVICE', 'WMS');
        url.searchParams.set('VERSION', '1.1.1');
        url.searchParams.set('REQUEST', 'GetMap');
        url.searchParams.set('LAYERS', 'poc:PM2.5_HI_2023_all');
        url.searchParams.set('FORMAT', 'image/jpeg');
        url.searchParams.set('TRANSPARENT', 'true');
        url.searchParams.set('SRS', 'EPSG:3857');
        url.searchParams.set('WIDTH', '256');
        url.searchParams.set('HEIGHT', '256');
        url.searchParams.set('BBOX', bounds);
        url.searchParams.set('SLD_BODY', sldBody);

        return url.toString();
      },
      tileSize: new google.maps.Size(256, 256),
      maxZoom: 18,
      minZoom: 0,
      opacity: 0.7,
      name: 'GeoServer Band1',
    });

    mapInstance.overlayMapTypes.insertAt(0, wmsOverlay);
  }, []);

  return (
    <LoadScript googleMapsApiKey="mymapkey">
      <GoogleMap
        mapContainerStyle={containerStyle}
        center={center}
        zoom={5}
        onLoad={onLoad}
      >
        {/* You can add markers, search boxes, etc. here */}
      </GoogleMap>
    </LoadScript>
  );
};

export default MapWithWmsOverlay;

Slow Javascript evaluation on Flutter WebView (iOS)

I am trying to run Javascript command to fetch the HTML from the Flutter WebView.

Javascript:

document.documentElement.outerHTML

Packages tried:

Problem: Running the above Javascript lags the user-interface. It takes like 15-20 seconds for the Javascript to return its result. In the meantime Xcode shows >100% CPU usage.

Running the same Javascript on native Swift iOS app returns immediately without any lag.

Web page used in the WebView is https://en.wikipedia.org/wiki/Hello_(Adele_song)


Code to reproduce issue with webview_flutter:

 class HtmlPreviewView extends StatelessWidget {

  const HtmlPreviewView({super.key});

  @override
  Widget build(BuildContext context) {

      final wv = WebViewController();

      Future.delayed(const Duration(seconds: 5), () {
        wv.runJavaScriptReturningResult('''document.documentElement.outerHTML.toString()''').then((result) {
          print(result.toString());
        });
      });

      WebViewWidget(
            controller: wv
              ..setJavaScriptMode(JavaScriptMode.unrestricted)
              ..loadRequest(
                WebUri('https://en.wikipedia.org/wiki/Hello_(Adele_song)'),
              ),
          );
  }
}

Code to reproduce issue with flutter_inappwebview:

  
 class HtmlPreviewView extends StatelessWidget {

  const HtmlPreviewView({super.key});

    /// JavaScript code to get the HTML content of the page
  String get getHtmlContentScript => '''
(function() {
  const htmlContent = document.documentElement.outerHTML;
  window.flutter_inappwebview.callHandler('onHtmlContent', htmlContent);
})();
  ''';


  @override
  Widget build(BuildContext context) {

    return InAppWebView(
            initialUrlRequest: 'https://en.wikipedia.org/wiki/Hello_(Adele_song)',
            initialSettings: InAppWebViewSettings(
              javaScriptEnabled: true,
              isInspectable: true,
            ),
            initialUserScripts: UnmodifiableListView([
              UserScript(
                source: linkLongPressScript,
                injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END,
              ),
            ]),
            onWebViewCreated: (controller) {
              state.setWebViewController(controller);

                controller.addJavaScriptHandler(
                  handlerName: 'onHtmlContent',
                  callback: (args) {
                    String htmlContent = args[0];
                    print(htmlContent);
                  },
              );
            }
          );
  }
}

Code to reproduce ideal native iOS performance:

import SwiftUI
import WebKit

struct ContentView: View {
    var body: some View {
        WebView(urlString: "https://en.wikipedia.org/wiki/Hello_(Adele_song)")
            .edgesIgnoringSafeArea(.all)
    }
}

struct WebView: UIViewRepresentable {
    let urlString: String

    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        webView.navigationDelegate = context.coordinator
        if let url = URL(string: urlString) {
            webView.load(URLRequest(url: url))
        }
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            webView.evaluateJavaScript("document.documentElement.outerHTML.toString()") { (html, error) in
                if let htmlString = html as? String {
                    print("HTML Source Code:n(htmlString)")
                } else if let error = error {
                    print("Error fetching HTML: (error.localizedDescription)")
                }
            }
        }
    }
}


#Preview {
    ContentView()
}

Buffering timed out after 10000ms

Whenever I run this js file using node, I first get a console log back saying databse is connected. However, i get an error message after saying “MongooseError: Operation campgrounds.deleteMany() buffering timed out after 10000ms”. Any idea on why this is? Even if i deleted the deleteMany({}) part of my code, it is another timeout error “MongooseError: Operation campgrounds.insertOne() buffering timed out after 10000ms”


const mongoose = require("mongoose");
const cities = require("./cities");
const { places, descriptors } = require("./seedHelpers");
const Campground = require("../models/campground");

mongoose.connect("mongodb://127.0.0.1:27017/camp-spot");

const db = mongoose.connection;

db.on("error", console.error.bind(console, "connection error:"));
db.once("open", () => {
  console.log("Database connected");
});

const sample = (array) => array[Math.floor(Math.random() * array.length)];

const seedDB = async () => {
  await Campground.deleteMany({});
  for (let i = 0; i < 50; i++) {
    const random1000 = Math.floor(Math.random() * 1000);
    const price = Math.floor(Math.random() * 20) + 10;
    const camp = new Campground({
      location: `${cities[random1000].city}, ${cities[random1000].state}`,
      title: `${sample(descriptors)} ${sample(places)}`,
      image: "https://source.unsplash.com/collection/483251",
      description:
        "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quibusdam dolores vero perferendis laudantium, consequuntur voluptatibus nulla architecto, sit soluta esse iure sed labore ipsam a cum nihil atque molestiae deserunt!",
      price,
    });
    await camp.save();
  }
};

seedDB().then(() => {
  mongoose.connection.close();
});

Unsure of what to do, tried to run this file with node using catch for errors, but still get the same error of just timing out.

Zoom out new origin calculation using css transform matrix

I am required to implement a pan and zoom feature for an image, my project is implemented from React, due to performance concerns, I couldn’t use normal useState and other APIs because of performance concerns. I went along with a vanillaJS code inspired from

https://github.com/kwdowik/zoom-pan/blob/master/src/renderer.js

My problem is when doing the zoom out,

export const hasPositionChanged = ({ pos, prevPos }: Position): boolean =>
  pos !== prevPos;
const valueInRange = ({ minScale, maxScale, scale }: ScaleRange): boolean =>
  scale <= maxScale && scale >= minScale;
const getTranslate =
  ({ minScale, maxScale, scale }: ScaleRange) =>
  ({ pos, prevPos, translate }: TranslateParams): number =>
    valueInRange({ minScale, maxScale, scale }) &&
    hasPositionChanged({ pos, prevPos })
      ? translate + (pos - prevPos * scale) * (1 - 1 / scale)
      : translate;
const getScale = ({
  scale,
  minScale,
  maxScale,
  scaleSensitivity,
  deltaScale,
}: ScaleParams): [number, number] => {
  let newScale = scale + deltaScale / (scaleSensitivity / scale);
  newScale = Math.max(minScale, Math.min(newScale, maxScale));
  return [scale, newScale];
};
const getMatrix = ({ scale, translateX, translateY }: MatrixParams): string =>
  `matrix(${scale}, 0, 0, ${scale}, ${translateX}, ${translateY})`;


const makeZoom = (state: RendererState): ZoomActions => ({
  zoom: ({ x, y, deltaScale }: ZoomParams) => {
    const { left, top } = state.element.getBoundingClientRect();
    const { minScale, maxScale, scaleSensitivity } = state;
    const [scale, newScale] = getScale({
      scale: state.transformation.scale,
      deltaScale,
      minScale,
      maxScale,
      scaleSensitivity,
    });

    const originX = x - left;
    const originY = y - top;
    const newOriginX = originX / scale;
    const newOriginY = originY / scale;
    const translate = getTranslate({ scale, minScale, maxScale });

    let translateX = translate({
      pos: originX,
      prevPos: state.transformation.originX,
      translate: state.transformation.translateX,
    });

    let translateY = translate({
      pos: originY,
      prevPos: state.transformation.originY,
      translate: state.transformation.translateY,
    });

    const transformOrigin = `${newOriginX}px ${newOriginY}px`;
    const transform = getMatrix({
      scale: newScale,
      translateX,
      translateY,
    });

    state.element.style.transformOrigin = transformOrigin;
    state.element.style.transform = transform;

    if (state.canvasElement) {
      state.canvasElement.style.transformOrigin = transformOrigin;
      state.canvasElement.style.transform = transform;
    }

    state.transformation = {
      originX: newOriginX,
      originY: newOriginY,
      translateX,
      translateY,
      scale: newScale,
    };
  },
});
export const renderer = ({
  minScale,
  maxScale,
  element,
  canvasElement,
  scaleSensitivity = 0.1,
}: RendererParams): RendererInstance => {
  const state: RendererState = {
    element,
    canvasElement,
    minScale,
    maxScale,
    scaleSensitivity,
    transformation: {
      originX: 0,
      originY: 0,
      translateX: 0,
      translateY: 0,
      scale: 1,
    },
  };

  if (canvasElement) {
    canvasElement.style.zIndex = "10";
  }

  return Object.assign({}, makeZoom(state));
};

you can view full code from the link above.

As you can observe

    const originX = x - left;
    const originY = y - top;
    const newOriginX = originX / scale;
    const newOriginY = originY / scale;

the new origin is calculated from the scale as well the mouse position and image position difference, when the image is zooming out, the value of scale becomes so small that the new origin values becomes very large, making the image to have unexpected translated positions. I went with transform matrix because I wanted to do the pan as well (the code is not included above), but my problem is with the origin calculation.

I found out using a clamp function is a solution, but it didn’t worked out for though.

If there are any suggestions, those would be most thankful.

Resizing cropper js canvas and image not working

I have the following vue.js code which opens a modal that allows the user to crop an area of a selected image for upload. The problem is that I cannot adjust the size of the canvas and the image to be larger than what is currently being displayed. When I explicitly set set the width and the size of the cropper modal modal/canvas and image to be 500px by 100px using the css, the canvas and image do not conform to the specified dimension.

How can I resolve this to create the intended effect?

Attempted css solution which did not work

enter image description here

.cropper-modal {
  height: 500px !important;
  width: 1000px !important;
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.7);
  height: 500px !important;
  width: 1000px !important;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cropper-modal-content {
  height: 500px !important;
  width: 1000px !important;
  background: #fff;
  padding: 2rem;
  border-radius: 8px;
  max-width: 90vw;
  max-height: 90vh;
  overflow: auto;
  text-align: center;
}
.cropper-modal-content img,
.cropper-modal-content .cropper-container,
.cropper-modal-content canvas{
  height: 500px !important;
  width: 1000px !important;
}

imageUpload.vue (existing code)

enter image description here

<template>
  <div class="col-md-9 p-0 w-80 feedback-tabs settings-row">
    <div class="p-1 p-md-4">
      <div class="row mb-2 mb-md-5 mt-2">
        <div class="col-md-6 my-auto">
          <div class="d-flex iwon-lost">
            <h2 class="mb-0 me-md-4">Settings</h2>
          </div>
        </div>
      </div>
      <ul class="nav nav-tabs border-0" role="tablist">
        <li class="nav-item" role="presentation">
          <a class="nav-link active" id="simple-tab-0" data-bs-toggle="tab" href="#general" role="tab" aria-controls="simple-tabpanel-0" aria-selected="true">General</a>
        </li>
        <li class="nav-item" role="presentation" v-if="user.subscription && user.subscription.allow_branding">
          <a class="nav-link" id="simple-tab-3" data-bs-toggle="tab" href="#branding" role="tab" aria-controls="simple-tabpanel-3" aria-selected="false">Branding</a>
        </li>
      </ul>
      <div class="tab-content pt-3 ps-4 pe-4 pb-2 pb-md-4 bg-white tab-mob-pad" id="tab-content">
        <div class="tab-pane active" id="general" role="tabpanel" aria-labelledby="simple-tab-0">
          <div class="row pt-3 mb-3">
            <div class="col-md-12">
              <h4 class="mb-2 poppins-semibold font-32">Profile Details</h4>
              <small>Update your profile detail here</small>
            </div>
          </div>
          <div class="row profile-info mt-4">
            <div class="col-md-6">
              <div class="d-flex justify-content-center justify-content-md-start align-items-center">
                <div class="user-pic position-relative photo-container">
                  <img v-if="profileImageFile" :src="profileImagePreview"  class="mb-3"/>
                  <img v-else-if="user.photo && user.photo.name" :src="`${base_url}/get-uploaded-image/${user.photo?.name}`" class="mb-3"/>
                  <img v-else src="../../../assets/images/john-doe.png" alt="avater"/>
                  <label v-if="isEdit" for="upload-pic" class="cam-icon">
                    <img src="../../../assets/images/camera.svg" alt="pic">
                    <input type="file" name="upload-pic" id="upload-pic" ref="profileImage" @change="handleProfileImageUpload()">
                  </label>
                </div>
                <div class="info">
                  <h5 class="mb-0 poppins-semibold font-24"> {{ user.first_name + ' ' + user.last_name }} </h5>
                  <small>{{ user.email }}</small>
                </div>
              </div>
            </div>
            <div class="col-md-6 text-md-end my-auto mt-5 mt-md-0 place-bid">
              <a v-if="!isEdit" href="#" class="btn-style p-2 ps-4 pe-4 mb-3 d-inline-block font-20" @click.prevent="isEdit = true">EDIT PROFILE</a>
              <br>
              <a v-if="isEdit" href="#" class="update-profile" @click.prevent="submit()">UPDATE PROFILE</a>
            </div>
          </div>
        </div>

        <div v-if="user.subscription && user.subscription.allow_branding" class="tab-pane update-profile-sec" id="branding" role="tabpanel" aria-labelledby="simple-tab-3">
          <div class="row pt-3 mb-3">
            <div class="col-md-12">
              <h4 class="mb-2 poppins-semibold font-32">Profile Details</h4>
              <small>Update your profile detail here</small>
            </div>
          </div>

          <div class="row profile-info mt-4">
            <div class="col-md-6">
              <div class="d-flex justify-content-center justify-content-md-start align-items-center">
                <div class="user-pic position-relative photo-container">
                  <img v-if="brandingProfileImageFile" :src="brandingProfileImagePreview"  class="mb-3"/>
                  <img v-else-if="branding.photo && branding.photo.name" :src="`${base_url}/get-uploaded-image/${branding.photo?.name}`" class="mb-3"/>
                  <img v-else src="../../../assets/images/john-doe.png" alt="avater"/>
                </div>
                <div class="info">
                  <h5 class="mb-0 poppins-semibold font-24"> {{ branding.name }} </h5>
                  <small>{{ branding.email }}</small>
                </div>
              </div>
            </div>
            <div class="col-md-6 place-bid justify-content-end align-items-center d-none d-md-flex">
              <a href="#" class="btn-style p-2 ps-4 pe-4 mb-3 d-inline-block font-20" @click.prevent="submitBranding()">Save Changes</a>
            </div>
          </div>

          <div class="row border-top form py-4">
            <div class="col-md-3 d-flex align-items-center">
              <label class="dark-grey font-24 poppins-med">Name</label>
            </div>
            <div class="col-md-3">
              <input class="w-100" type="text" name="" v-model="branding.name" :class="{ 'border-danger': v$.branding.name.$error }">
              <p class="text-danger" v-if="v$.branding.name.$errors[0]?.$validator === 'required' && v$.branding.name.$errors[0]?.$property === 'name'">
                {{v$.branding.name.required.$message}}
              </p>
            </div>
            <div class="col-md-3 d-none d-md-flex align-items-center">
              <label class="dark-grey font-24 poppins-med">Email</label>
            </div>
            <div class="col-md-3 d-none d-md-block">
              <input class="w-100" type="email" name="" v-model="branding.email" :class="{ 'border-danger': v$.branding.email.$error }">
              <p class="text-danger" v-if="v$.branding.email.$errors[0]?.$validator === 'required' && v$.branding.email.$errors[0]?.$property === 'email'">
                {{v$.branding.email.required.$message}}
              </p>
            </div>
          </div>

          <div class="row border-top form py-4 d-md-none">
            <div class="d-flex align-items-center">
              <label class="dark-grey font-24 poppins-med">Email</label>
            </div>
            <div>
              <input class="w-100" type="email" name="" v-model="branding.email" :class="{ 'border-danger': v$.branding.email.$error }">
              <p class="text-danger" v-if="v$.branding.email.$errors[0]?.$validator === 'required' && v$.branding.email.$errors[0]?.$property === 'email'">
                {{v$.branding.email.required.$message}}
              </p>
            </div>
          </div>

          <div class="row border-top form py-4">
            <div class="col-md-3 d-flex align-items-center">
              <label class="dark-grey font-24 poppins-med">Profile Image</label>
            </div>
            <div class="col-md-3">
              <input class="w-100" type="file" name="upload-branding-profile" id="upload-branding-profile" ref="brandingProfileImage" @change="handleBrandingProfileImageUpload()" hidden>
              <input class="w-100" type="text" name="" :value="brandingProfileImageFile?.name ?? branding.photo?.name" readonly>
            </div>
            <div class="col-md-6 d-flex align-items-center justify-content-end place-bid">
              <a href="#" class="btn-style p-2 ps-4 pe-4 d-inline-block font-20 bg-black" @click.prevent="handleClickBrandingProfileImageUpload()">Upload File</a>
            </div>
          </div>
          
          <div class="row border-top form py-4">
            <div class="col-md-3 d-flex align-items-center">
              <label class="dark-grey font-24 poppins-med">Brand Logo</label>
            </div>
            <div class="col-md-3">
              <input class="w-100" type="file" name="upload-logo" id="upload-logo" ref="brandingLogoImage" @change="handleBrandingLogoImageUpload()" hidden>
              <input class="w-100" type="text" name="" :value="brandingLogoImageFile?.name ?? branding.logo?.name" readonly>
            </div>
            <div class="col-md-4 dark-grey font-18">
              The logo image aspect ratio should be 7:1
            </div>
            <div class="col-md-2 d-flex align-items-center justify-content-end place-bid">
              <a href="#" class="btn-style p-2 ps-4 pe-4 d-inline-block font-20 bg-black" @click.prevent="handleClickBrandingLogoImageUpload()">Upload File</a>
            </div>
          </div>

          <div class="row border-top form py-4">
            <div class="col-md-3 d-flex align-items-center">
              <label class="dark-grey font-24 poppins-med">Brand Banner</label>
            </div>
            <div class="col-md-3">
              <input class="w-100" type="file" name="upload-banner" id="upload-banner" ref="brandingBannerImage" @change="handleBrandingBannerImageUpload()" hidden>
              <input class="w-100" type="text" name="" :value="brandingBannerImageFile?.name ?? branding.banner?.name" readonly>
            </div>
            <div class="col-md-4 dark-grey font-18">
              The banner image aspect ratio should be 125:20
            </div>
            <div class="col-md-2 d-flex align-items-center justify-content-end place-bid">
              <a href="#" class="btn-style p-2 ps-4 pe-4 d-inline-block font-20 bg-black" @click.prevent="handleClickBrandingBannerImageUpload()">Upload File</a>
            </div>
          </div>

          <div class="row border-top form py-4">
            <div class="col-md-3 d-flex align-items-center">
              <label class="dark-grey font-24 poppins-med">Website Link</label>
            </div>
            <div class="col-md-9">
              <input class="w-100" type="text" name="" v-model="branding.link">
            </div>
          </div>

          <div class="row border-top form py-4">
            <div class="col-md-3 d-flex">
              <label class="dark-grey font-24 poppins-med">Brand Description</label>
            </div>
            <div class="col-md-9">
              <textarea type="text" name="" rows="6" v-model="branding.description"></textarea>
            </div>

            <div class="col-12 d-md-none">
              <a href="#" class="btn-style p-2 ps-4 pe-4 mb-3 d-inline-block font-20 w-100" @click.prevent="submitBranding()">Save Changes</a>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <!-- Cropper Modal -->
  <div v-if="showCropperModal" class="cropper-modal">
    <div class="cropper-modal-content">
      <img :src="cropperImageUrl" ref="cropperImage" style="max-width:100%;" />
      <div class="mt-3">
        <button @click="cropImage" class="btn btn-success">Crop & Save</button>
        <button @click="closeCropper" class="btn btn-secondary">Cancel</button>
      </div>
    </div>
  </div>


</template>
<script>
import 'https://js.stripe.com/v3/'
import { GOOGLE_MAP_API_KEY } from '@/constants'
import DeleteConfirmationModal from '@/components/Modal/DeleteConfirmationModal.vue'
import ConfirmationModal from '@/components/Modal/ConfirmationModal.vue'
import { useVuelidate } from '@vuelidate/core'
import { helpers, required, email, requiredIf, sameAs, integer, minValue, minLength } from '@vuelidate/validators'
import { toast } from 'vue-sonner'
import { RepositoryFactory } from '@/repositories'
import Cropper from 'cropperjs'

const Profile = RepositoryFactory.get('profile')
const imageUploader = RepositoryFactory.get('imageUploader')
const Branding = RepositoryFactory.get('branding')

export default {
  name: 'UserSettings',
  components: {
    DeleteConfirmationModal, ConfirmationModal
  },
  data() {
    return {
      base_url: import.meta.env.VITE_API_URL,
      stripeKey: import.meta.env.VITE_STRIPE_PUBLIC_KEY,
      gMapApiKey: GOOGLE_MAP_API_KEY,
      pageTitle: "Add New User",
      userId: this.$route.params.id ? this.$route.params.id : null,
      isEdit: false,
      user: {
        username: "",
        first_name: "",
        last_name: "",
        email: "",
        phone_number: "",
        address: "",
        country: null,
        photo: ""
      },
      branding: {
        name: "",
        email: "",
        photo: "",
        logo: "",
        banner: "",
        link: "",
        description: "",
      },
      profileImageFile: null,
      profileImagePreview: null,
      brandingProfileImageFile: null,
      brandingProfileImagePreview: null,
      brandingLogoImageFile: null,
      brandingBannerImageFile: null,
      agentImageFile: null,
      agentImagePreview: null,
      showDeleteModal: false,
      isImageError: false,
      v$: useVuelidate(),
      showCropperModal: false,
      cropper: null,
      cropperImageType: null, // 'logo' or 'banner'
      cropperImageFile: null,
      cropperImageUrl: null,
    }
  },
  mounted() {
    this.getUserProfile()
    this.getBranding()
    
  },
  validations() {
    return {
      branding: {
        name: {
          required: helpers.withMessage(
            'Name is required',
            required
          )
        },
        email: {
          required: helpers.withMessage(
            'Email is required',
            required
          ),
          email: helpers.withMessage(
            'Please enter a valid email',
            email
          )
        },
      }
    }
  },
  methods: {
    getUserProfile() {
      Profile.getProfile().then(response => {
        if (response.status == 200) {
          this.user = response.data.profile
          if (this.user.platform === 'web' || this.user.platform === 'android') {
            this.setupStripe()
          }
        }
      })
    },
    getBranding() {
      this.setLoader(true)
      Branding.getBranding().then(response => {
        if (response.status === 200) {
          this.branding = response.data.branding
        }
        this.setLoader(false)
      }).catch(response => {
        this.setLoader(false)
      })
    },
    async submit() {
      this.v$.user.$touch()
      if (!this.user.country && this.user.address) {
        this.addressError = true
        toast.error("Please select address from dropdown")
        return
      }
      if (!this.v$.user.$error) {
        let profileImageId = null
        try {
          [profileImageId] = await Promise.all([
            this.profileImageFile ? this.imageUpload(this.profileImageFile) : null
          ]);

          if (profileImageId) {
            this.user.photo = profileImageId;
          }

          Profile.update(this.user).then(response => {
            if (response.status == 200) {
              toast.success(response.data.message)
              let profileData = this.profile
              profileData.user.photo = response.data.updated.photo
              profileData.user.first_name = response.data.updated.first_name
              profileData.user.last_name = response.data.updated.last_name
              profileData.user.address = response.data.updated.address
              profileData.user.country = response.data.updated.country
              this.setProfile(profileData)
              this.isEdit = false
              this.isImageError = false
            }
          }).catch(response => {
            if (profileImageId) {
              imageUploader.DeleteImage(profileImageId)
            }
            toast.error(response.response.data.message)
          })
        } catch (error) {
          toast.error(error);
        }
      }
      this.setLoader(false)
    },
    async submitBranding() {
      this.v$.branding.$touch()
      if (!this.v$.branding.$error) {
        let profileImageId = null
        let logoImageId = null
        let bannerImageId = null
        try {
          [profileImageId, logoImageId, bannerImageId] = await Promise.all([
            this.brandingProfileImageFile ? this.imageUpload(this.brandingProfileImageFile) : null,
            this.brandingLogoImageFile ? this.imageUpload(this.brandingLogoImageFile) : null,
            this.brandingBannerImageFile ? this.imageUpload(this.brandingBannerImageFile) : null
          ]);

          if (profileImageId) {
            this.branding.photo = profileImageId;
          }

          if (logoImageId) {
            this.branding.logo = logoImageId;
          }

          if (bannerImageId) {
            this.branding.banner = bannerImageId;
          }

          Branding.updateBranding(this.branding).then(response => {
            if (response.status === 200) {
              toast.success(response.data.message)
              let brandingData = this.branding
              brandingData.name = response.data.updated.name
              brandingData.email = response.data.updated.email
              brandingData.photo = response.data.updated.photo
              brandingData.logo = response.data.updated.logo
              brandingData.banner = response.data.updated.banner
              brandingData.link = response.data.updated.link
              brandingData.description = response.data.updated.description
            }
          }).catch(response => {
            if (logoImageId) {
              imageUploader.DeleteImage(logoImageId)
            }
            if (bannerImageId) {
              imageUploader.DeleteImage(bannerImageId)
            }
            toast.error(response.response.data.message)
          })
        } catch (error) {
          toast.error(error);
        }
      }
      this.setLoader(false)
    },
    handleSelectAgent() {
      if (!this.agent._id) {
        this.resetAgent()
      } else {
        this.agentImageFile = null;
        this.agentImagePreview = null;
        this.agent = { ...this.agents.find(agent => agent._id === this.agent._id) }
      }
    },
    handleAgentImageUpload() {
      const agentImage = this.$refs.agentImage;
      if (agentImage.files.length > 0) {
        const selectedFile = agentImage.files[0];
        this.agentImageFile = selectedFile;
        this.agentImagePreview = selectedFile;

        const reader = new FileReader();
        reader.onload = () => {
          this.agentImagePreview = reader.result;
        };
        reader.readAsDataURL(selectedFile);

        agentImage.value = '';
      } else {
        this.agentImageFile = null;
        this.agentImagePreview = null;
      }
    },
    handleClickAgentImageUpload() {
      document.getElementById('upload-agent').click()
    },
    handleProfileImageUpload() {
      const profileImage = this.$refs.profileImage;
      if (profileImage.files.length > 0) {
        const selectedFile = profileImage.files[0];
        this.profileImageFile = selectedFile;
        this.profileImagePreview = selectedFile;

        const reader = new FileReader();
        reader.onload = () => {
          this.profileImagePreview = reader.result;
        };
        reader.readAsDataURL(selectedFile);

        profileImage.value = '';
      } else {
        this.profileImageFile = null;
        this.profileImagePreview = null;
      }
    },
    handleClickBrandingProfileImageUpload() {
      document.getElementById('upload-branding-profile').click()
    },
    handleBrandingProfileImageUpload() {
      const profileImage = this.$refs.brandingProfileImage;
      if (profileImage.files.length > 0) {
        const selectedFile = profileImage.files[0];
        this.brandingProfileImageFile = selectedFile;
        this.brandingProfileImagePreview = selectedFile;

        const reader = new FileReader();
        reader.onload = () => {
          this.brandingProfileImagePreview = reader.result;
        };
        reader.readAsDataURL(selectedFile);

        profileImage.value = '';
      } else {
        this.brandingProfileImageFile = null;
        this.brandingProfileImagePreview = null;
      }
    },
    handleClickBrandingLogoImageUpload() {
      document.getElementById('upload-logo').click()
    },
    handleBrandingLogoImageUpload() {
      const logoImage = this.$refs.brandingLogoImage;
      if (logoImage.files.length > 0) {
        const selectedFile = logoImage.files[0];
        this.openCropper(selectedFile, 'logo');
        logoImage.value = '';
      } else {
        this.brandingLogoImageFile = null;
      }
    },
    handleClickBrandingBannerImageUpload() {
      document.getElementById('upload-banner').click()
    },
    handleBrandingBannerImageUpload() {
      const bannerImage = this.$refs.brandingBannerImage;
      if (bannerImage.files.length > 0) {
        const selectedFile = bannerImage.files[0];
        this.openCropper(selectedFile, 'banner');
        bannerImage.value = '';
      } else {
        this.brandingBannerImageFile = null;
      }
    },
    openCropper(file, type) {
      this.cropperImageType = type;
      this.cropperImageFile = file;
      this.cropperImageUrl = URL.createObjectURL(file);
      this.showCropperModal = true;

      this.$nextTick(() => {
        const image = this.$refs.cropperImage;
        if (!image) {
          console.error('Cropper target image element not found (this.$refs.cropperImage).');
          this.closeCropper(); // Close modal if image ref is missing
          return;
        }

        if (this.cropper && typeof this.cropper.destroy === 'function') {
          this.cropper.destroy();
        }
        
        try {

          this.cropper = new Cropper(image, {
            aspectRatio: type === 'logo' ? 7 / 1 : 125 / 20,
            viewMode: 1,
            autoCrop: true,
            responsive: true,
            restore: false,
            checkCrossOrigin: false,
            ready: () => {
              console.log('Cropper.js ready event fired.');
              if (this.cropper && typeof this.cropper.getCropperCanvas === 'function') {
                console.log('In Cropper ready: getCropperCanvas IS a function.');
              } else {
                console.error('In Cropper ready: getCropperCanvas IS NOT a function or cropper is null. this.cropper:', this.cropper);
              }
            }
          });

          if (this.cropper && typeof this.cropper.getCropperCanvas === 'function') {
            console.log('Cropper instance created. getCropperCanvas method exists.');
          } else {
            console.error('Cropper instance created, but getCropperCanvas method DOES NOT exist or cropper is null. this.cropper:', this.cropper);
          }
        } catch (error) {
          console.error('Error initializing Cropper.js:', error);
          toast.error('Failed to initialize image cropper.');
          this.closeCropper();
        }
      });
    },
    async cropImage() {
      if (!this.cropper) {
        console.error('Cropper not initialized (this.cropper is null or undefined).');
        toast.error('Cropper is not ready.');
        return;
      }
    
      const selection = this.cropper.getCropperSelection?.();
      if (!selection || typeof selection.$toCanvas !== 'function') {
        console.error('Cannot crop image: selection or $toCanvas is missing.');
        toast.error('Cannot crop image: selection method is missing.');
        return;
      }
    
      try {
        const canvas = await selection.$toCanvas({
          maxWidth: 4096,
          maxHeight: 4096,
          fillColor: '#fff',
          imageSmoothingEnabled: true,
          imageSmoothingQuality: 'high',
        });
    
        if (!canvas) {
          console.error('Failed to get cropped canvas');
          toast.error('Failed to crop image.');
          return;
        }
    
        canvas.toBlob(blob => {
          if (!blob) {
            console.error('Failed to create blob from canvas');
            toast.error('Failed to create image blob.');
            return;
          }
    
          const croppedFile = new File([blob], this.cropperImageFile.name, {
            type: this.cropperImageFile.type,
          });
    
          if (this.cropperImageType === 'logo') {
            this.brandingLogoImageFile = croppedFile;
          } else if (this.cropperImageType === 'banner') {
            this.brandingBannerImageFile = croppedFile;
          }
    
          this.closeCropper();
          console.log('Cropped image:', 'image');
        }, this.cropperImageFile.type);
      } catch (error) {
        console.error('Error during crop operation:', error);
        toast.error('Failed to crop image.');
      }
    },
    closeCropper(){
      if (this.cropper && typeof this.cropper.destroy === 'function') {
        this.cropper.destroy();
      }
      this.cropper = null;
      this.showCropperModal = false;
      this.cropperImageUrl = null;
      this.cropperImageFile = null;
      this.cropperImageType = null;
    },
    async imageUpload(file) {
      return new Promise((resolve, reject) => {
        imageUploader.Upload(file)
          .then(response => {
            if (response.status === 200) {
              resolve(response.data.document[0]);
            } else {
              reject('Image upload failed');
            }
          })
          .catch((e) => {
            reject('Something went wrong');
          });
      });
    },
    toggleDeleteModal(show = false) {
      this.showDeleteModal = show;
    }

  }
}
</script>
<style>
.cam-icon {
  display: none;
}
.photo-container:hover .cam-icon {
  display: inline-block;
}
.text-danger {
  padding-left: 0;
}
.bg-gray {
  background-color: #f5f5f5;
}

.cropper-modal {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.7);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cropper-modal-content {
  background: #fff;
  padding: 2rem;
  border-radius: 8px;
  max-width: 90vw;
  max-height: 90vh;
  overflow: auto;
  text-align: center;

}
</style>

2 Eye Icons in password, and I want to remove the other one

I’m having trouble here and I need help, there’s always two Eye icon for Password toggle and I want to remove the big one. I want to remove the first eye Icon. HTML for password input, then JS is for the Password Visibility toggle and my CSS for the form. Here is my code.

         <div class="form-group">
                        <label for="password">Password</label>
                        <div class="input-with-icon">
                            <input type="password" name="password" id="password" class="form- 
   control {{$errors->has('password')?'error':''}}" placeholder="Enter your password" />
                            <i class="fa fa-lock"></i>
                            <span class="password-toggle" id="togglePassword" style="right: 
    25px;">
                                <i class="fa fa-eye"></i>
                            </span>
                        </div>
                        <span class="error-text"><b>{!!$errors->first('password', ':message')!!} 
   </b></span>
                    </div>
  .form-control {
  width: 100%;
  padding: 0.8rem 1rem 0.8rem 2.8rem;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.8);
  font-size: 1rem;
  transition: var(--transition);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
  }

 .password-toggle {
  position: absolute;
  right: 15px;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  color: #aaa;
  transition: var(--transition);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
 }
  $("#togglePassword").click(function() {
            var passwordInput = $("#password");
            var icon = $(this).find("i");

            if (passwordInput.attr("type") === "password") {
                passwordInput.attr("type", "text");
                icon.removeClass("fa-eye").addClass("fa-eye-slash");
            } else {
                passwordInput.attr("type", "password");
                icon.removeClass("fa-eye-slash").addClass("fa-eye");
            }
        });

Image of the output

screenshot

How can I use TeaVM to convert Java code into WebAssembly (Wasm) for use in web pages?

Java code example

package com.lson;

import org.teavm.jso.JSExport;

public class JsTest {

    private static JsTest instance = new JsTest();

    public static void main(String[] args) {
        // 这个main方法只是为了让TeaVM有入口点编译
        add(1, 2);
    }

    //@JSExport注解将方法暴露给JS调用,否则无法使用
    @JSExport
    public static JsTest getInstance() {
        return instance;
    }

    @JSExport
    public static int add(int a, int b) {
        int i = a + b;
        System.out.println("add:" + i);
        return i;
    }

    @JSExport
    public int multiply(int a, int b) {
        int i = a * b;
        System.out.println("multiply:" + i);
        return i;
    }
}

maven plugin

            <plugin>
                <groupId>org.teavm</groupId>
                <artifactId>teavm-maven-plugin</artifactId>
                <version>0.12.0</version>
                <dependencies>
                    <dependency>
                        <groupId>org.teavm</groupId>
                        <artifactId>teavm-classlib</artifactId>
                        <version>0.12.0</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <phase>process-classes</phase>
                        <configuration>
                            <mainClass>com.lson.JsTest</mainClass>
                            <mainPageIncluded>true</mainPageIncluded>
                            <debugInformationGenerated>true</debugInformationGenerated>
                            <sourceMapsGenerated>true</sourceMapsGenerated>
                            <minifying>false</minifying>
                            <runtime>org.teavm:teavm-jso:0.12.0</runtime>
                            <targetType>WEBASSEMBLY</targetType>
<!--                            <targetType>JAVASCRIPT</targetType>-->
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Then execute mvn package
The following files are generated:

classes.wasm

classes.wasm.c

classes.wasm-runtime.js

classes.wast

I use the example from the official website https://www.teavm.org/docs/intro/getting-started.html to load on an HTML page

<!DOCTYPE html>
<html>
<head>
    <title>TeaVM Test</title>
    
    
        <script type="text/javascript" charset="utf-8" src="classes.wasm-runtime.js"></script>
     <script>
      async function main() {
          let teavm = await TeaVM.wasmGC.load("classes.wasm");
          console.log(teavm)
          teavm.exports.main([]);
      }
    </script>
</head>

 <body onload="main()"></body>

</html>

The following error will be output in the console:
wasm2.html:10 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading ‘load’)
at main (wasm2.html:10:42)
at onload (wasm2.html:17:24)

using MutationObserver to detect when content/children of an element has overflowed that element

Below is my react custom hook to detect when the content/children of an element/ref.current, has overflowed that element/ref.current.

export function useOverflowObserver({
  ref,
  on_resize_cb,
  ref_reset_trigger = [],
}) {
  const [unexpectedly_overflowed, set_unexpectedly_overflowed] =
    useState(false); // false, true

  const overflow_checker_cb = () => {
    if (ref && ref.current) {
      console.log(ref.current.scrollHeight);
      console.log(ref.current.scrollWidth);

      console.log(ref.current.clientHeight);
      console.log(ref.current.clientWidth);

      const has_overflow =
        ref.current.scrollHeight > ref.current.clientHeight ||
        ref.current.scrollWidth > ref.current.clientWidth;

      set_unexpectedly_overflowed(has_overflow);
      on_resize_cb && on_resize_cb(has_overflow);
    } else {
      console.log("overflow_checker_cb: no ref provided to useResizeDetector");
    }
  };

  // useMemo is optional
  const observer = useMemo(() => {
    return new ResizeObserver(overflow_checker_cb);
  }, []);

  // useMemo is optional
  const mutation_observer = useMemo(() => {
    return new MutationObserver(overflow_checker_cb);
  }, []);

  useEffect(() => {
    if (ref && ref.current) {
      observer.observe(ref.current);
      mutation_observer.observe(ref.current, {
        attributes: true, // needed
        childList: true,
        subtree: true,
        characterData: true,
      });
    } else {
      console.log("no ref provided to useResizeDetector");
    }

    return () => {
      observer.disconnect();
      mutation_observer.disconnect();
    };
  }, ref_reset_trigger);

  return unexpectedly_overflowed;
}

Initially, I used these configuration options for the MutationObserver:

{
        childList: true,
        subtree: true,
        characterData: true,
}

But it didn’t work. So I added the:

{
        attributes: true,
        childList: true,
        subtree: true,
        characterData: true
}

And now it works.

So the question is, are there any other configuration properties that I need to enable? Or is this list truely exhaustive for the purpose of detecting when content/children of an element has overflowed their parent, ie, the element/ref.current?

Converting a XLSX to a temp Google sheet update and back to XLSX, while keeping the original fileID

When converting a .xlsx file to a temp google sheet file, update and back to .xlsx file keeps poping up an error, creating a pdf temp file, here is some code for reference, Error: Updating XLSX for xxxx : Converting from application/pdf to application/vnd.openxmlformats-officedocument.spreadsheetml.sheet is not supported. patial code ||. const sourceFile = DriveApp.getFileById(sourceSheetId);
const mimeType = sourceFile.getMimeType();
if (mimeType === ‘application/pdf’) return null;
if (mimeType === ‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet’) {
const resource = { name: ${clientName} Content Calendar (Converted), mimeType: ‘application/vnd.google-apps.spreadsheet’ };
const convertedFile = Drive.Files.copy(resource, sourceSheetId, { convert: true });
sheetId = convertedFile.id;
masterSheet.getRange(rowIndex, 6).setValue(sheetId);
} else if (mimeType !== ‘application/vnd.google-apps.spreadsheet’) {
return null;

VAST ads not working properly on Safari iOS and Samsung TV with VideoJS Nuevo plugin

I’m implementing VAST 3.0 preroll ads using the Nuevo plugin for VideoJS. While the implementation works perfectly on desktop browsers, I’m facing two specific platform issues:

  1. Safari 18.4 on iOS 18.4: No ads are played at all
  2. Samsung Smart TV: Only the first ad in the ad break plays (the break contains 3 consecutive ads)

Implementation Details

I’m loading the VAST/VPAID plugin dynamically after the player is ready:

player.on("ready", function () {
  window.videojs = videojs;
  const script = document.createElement("script");
  script.src = "/videojs/plugins/vast.vpaid.min.js";
  document.body.appendChild(script);

  script.onload = () => {
    player.vastAds({
      tagURL: vMapUrl, // VMAP URL from ad server
    });
  };
});

The VMAP URL is fetched from our ad server and contains a preroll ad break with three consecutive ads.

Questions

  1. Are there known compatibility issues with VAST/VPAID on Safari iOS that would prevent ads from playing entirely?
  2. What could cause only the first ad in a break to play on Samsung TVs?
  3. Is there a better way to implement this that would work across all platforms?

Here’s a simplified version of our player initialization:

const player = videojs(videoRef.current, {
  // ... player options
  sources: [{
    src: videoUrl,
    type: "application/x-mpegURL"
  }]
});

player.nuevo(); // Initialize Nuevo plugin

// Then the VAST implementation shown above

Any insights or suggestions for debugging these platform-specific issues would be greatly appreciated.

How to resolve this error: Could not resolve “virtual:keystatic-config”

I am using astro.js + keystatic.js (cms) to build a website with admin page, whenever i am running npm run dev command, i am seeing this error in terminal. it was working good earlier.

keystatic on github mode. The most interesting thing it doesn’t appear again when i close the terminal and start fresh. why does this happens?

note: i couldn’t use/create keystatic tag because of stackoverflow less reputation, please somebody make.

specs:
“@keystatic/astro”: “^5.0.6”,
“@keystatic/core”: “^0.5.47”,
“astro”: “^5.4.2”,

Display TVheadend stream in HTML5 video element without transcoding

I try to display raw stream from TVheadend in <video> element and cant get it to work in Firefox and Chrome. Also I get same error in https://github.com/4gray/iptvnator IPTV player when I try to use those stream urls inside .m3u list.

I have Traefik proxy that handles CORS headers, https and tunneling, all that works. But in <video> element it just downloads raw data without rendering image and audio.

I use OrangePi and Docker and transcoding to another format is not an option, it takes 100% CPU, I want to avoid such load. Without transcoding CPU load is 1%.

Here is index.html with <video> element:

<!DOCTYPE html>
<html>
<head>
  <title>TVHeadend Stream</title>
  <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
  <video id="video" muted controls autoplay width="640" height="360" type="video/webm"></video>

  <script>
    var video = document.getElementById('video');
    var videoSrc = 'https://my-tv.my-website.com/stream/channelid/1974776170?profile=pass';
    // var videoSrc = 'https://my-tv.my-website.com/stream/channelid/1974776170?profile=webtv-h264-aac-matroska';

    if (Hls.isSupported()) {
      var hls = new Hls();
      hls.loadSource(videoSrc);
      hls.attachMedia(video);
      hls.on(Hls.Events.MANIFEST_PARSED, function() {
        video.play();
      });
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      // Native HLS support (Safari)
      video.src = videoSrc;
      video.addEventListener('loadedmetadata', function() {
        video.play();
      });
    } else {
      console.error('HLS not supported');
    }
  </script>
</body>
</html>

Here is sample .m3u list that works in VLC player but fails in browser in IPTV players:

#EXTM3U
#EXTINF:-1 tvg-logo="https://my-tv-my-website.com/imagecache/41" tvg-id="7eb1b4f54aec2b3f89d1ad8d10a6674c",PINK
https://my-tv.my-website.com/stream/channelid/1974776170?profile=pass

Here are available streaming formats in TVheadend:

TVheadend version:

Codec information in VLC player:

I am runing WinTV-dualHD dvb-t2 TV card:

https://www.hauppauge.com/pages/products/data_dualhd.html

How to render TVheadend raw stream in browsers HTML5 <video> element and web .m3u players without transcoding?