‘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

Using property in another method in same class in different HTTP requests

I’m new to OOP in php and I’ve the following issue.
Basically I’ve visit form which submitted data to create action in the controller.. after that it’s redirect to success action which outputting visit counter depend on submitted data.

My question now is how I can store posted data inside a property in create method then use it in success method ?

Maybe I didn’t explain my issue very well for that please check my code below:

addvisit.php

public function createAction()
    {
        $visit = new Visit($_POST);
        $plate = $_POST['plate'];

        if ($visit->save()) {
            
            $this->redirect('/AddVisit/success');

        } else {

            View::renderTemplate('Visits/new.html', [
                'visit' => $visit
            ]);

        }
    }

    public function successAction()
    {
        $visit = new Visit();
        $visit->countVisit($this->createAction()->plate);

        View::renderTemplate('Visits/success.html', [
                'visit' => $visit
            ]);
    }

I’m trying also to use return in create method like below but it’s didn’t work:

public function createAction()
    {
        $visit = new Visit($_POST);
        $plate = $_POST['plate'];

        if ($visit->save()) {
            
            $this->redirect('/AddVisit/success');
            return $plate;

FYI this is also countVisit method in my model:

public function countVisit($plate)
    {
        $count = static::findCar($plate);

        // Visit less than required qty for free
        if ($this->subscribe->isMember($plate)) {
            $remain = $this->subscribe->isMember($plate)->subscribe_qty;
            echo "Remainnig visit <h3>$remain</h3>";
        } elseif ($count->qty >= 3) {
            $this->resetCount($plate);
            echo "<h3>You got a free visit</h3>";
        } else {
            $current = $count->qty;
            echo "This is the visit number <h3>$current</h3>";
        }

    }

I didn’t put the full code and trying to focus on my problem part.

Thank you.

Getting Session start error message in debug log file on my WordPress site

I am getting these two session start errors on my site and I have done everything to fix but they persist.

[26-May-2025 03:30:02 UTC] PHP Warning: session_start(): Session cannot be started after headers have already been sent in /home/u402881756/domains/realrender3d.app/public_html/books/wp-content/plugins/custom-books/includes/form-handler.php on line 5
[26-May-2025 03:30:02 UTC] PHP Warning: session_start(): Session cannot be started after headers have already been sent in /home/u402881756/domains/realrender3d.app/public_html/books/wp-content/plugins/custom-books/book-personalizer.php on line 12

Here is my main plugin file:

    <?php
/**
 * Plugin Name: Custom Book Personalizer
 * Description: A plugin for personalizing children's books.
 * Version: 1.0
 * Author: Nastin Gwaza
 */
if (!defined('ABSPATH')) exit; // Exit if accessed directly

add_action('plugins_loaded', function () {
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
}, 1); // Run very early

// ✅ Plugin constants
define('BP_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('BP_PLUGIN_URL', plugin_dir_url(__FILE__));

// ✅ Load includes
$includes = [
    BP_PLUGIN_DIR . 'includes/scripts.php',
    BP_PLUGIN_DIR . 'includes/admin-product-meta.php',
    BP_PLUGIN_DIR . 'includes/woocommerce-hooks.php',
    BP_PLUGIN_DIR . 'includes/settings.php',
    BP_PLUGIN_DIR . 'includes/face-swap-handler.php', // Added AI image generator
];

foreach ($includes as $file) {
    if (file_exists($file)) {
        require_once $file;
    }
}

// ✅ Preview page shortcode
add_shortcode('book_preview_page', function () {
    ob_start();
    include BP_PLUGIN_DIR . 'includes/shortcode-preview-page.php';
    return ob_get_clean();
});

// ✅ Personalization form shortcode with product_id handling
function bp_render_personalization_page($atts) {
    $atts = shortcode_atts([
        'product_id' => get_the_ID()
    ], $atts);

    $product_id = intval($atts['product_id']);

    ob_start();
    include BP_PLUGIN_DIR . 'includes/personalization-page.php';
    return ob_get_clean();
}

function bp_register_shortcodes_after_wc_loaded() {
    if (function_exists('wc_get_product')) {
        add_shortcode('book_personalizer', 'bp_render_personalization_page');
    }
}
add_action('init', 'bp_register_shortcodes_after_wc_loaded');

// ✅ Hook form submission
add_action('admin_post_bp_handle_form', 'bp_handle_form_submission');
add_action('admin_post_nopriv_bp_handle_form', 'bp_handle_form_submission');
add_action('admin_post_bp_add_to_cart', 'bp_add_to_cart_from_preview');
add_action('admin_post_nopriv_bp_add_to_cart', 'bp_add_to_cart_from_preview');



require_once BP_PLUGIN_DIR . 'includes/form-handler.php';


// 1. Add personalization data to cart item
add_filter('woocommerce_add_cart_item_data', function($cart_item_data, $product_id, $variation_id) {
    if (isset($_POST['personalization_data'])) {
        $cart_item_data['personalization_data'] = json_decode(stripslashes($_POST['personalization_data']), true);
        $cart_item_data['unique_key'] = md5(microtime().rand()); // Prevent merging similar items
    }
    return $cart_item_data;
}, 10, 3);

// 2. Show personalization on cart and checkout
add_filter('woocommerce_get_item_data', function($item_data, $cart_item) {
    if (isset($cart_item['personalization_data'])) {
        $pdata = $cart_item['personalization_data'];

        if (!empty($pdata['child_name'])) {
            $item_data[] = [
                'key'   => 'Child's Name',
                'value' => sanitize_text_field($pdata['child_name']),
            ];
        }

        if (!empty($pdata['age_group'])) {
            $item_data[] = [
                'key'   => 'Age Group',
                'value' => sanitize_text_field($pdata['age_group']),
            ];
        }

        if (!empty($pdata['gender'])) {
            $item_data[] = [
                'key'   => 'Gender',
                'value' => sanitize_text_field($pdata['gender']),
            ];
        }
    }

    return $item_data;
}, 10, 2);

// 3. Save personalization in order meta
add_action('woocommerce_add_order_item_meta', function($item_id, $values) {
    if (!empty($values['personalization_data'])) {
        wc_add_order_item_meta($item_id, 'Personalization Details', $values['personalization_data']);
    }
}, 10, 2);

and here is my form-handler.php file:

    <?php
defined('ABSPATH') || exit;

if (!session_id()) {
    session_start();
}

add_action('admin_post_nopriv_bp_handle_form_submission', 'bp_handle_form_submission');
add_action('admin_post_bp_handle_form_submission', 'bp_handle_form_submission');

function bp_handle_form_submission() {
    if (!isset($_POST['bp_personalize_nonce']) || !wp_verify_nonce($_POST['bp_personalize_nonce'], 'bp_personalize_action')) {
        $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
        wp_redirect(home_url('/personalize-book?error=invalid_nonce&product_id=' . $product_id));
        exit;
    }

    $required_fields = ['product_id', 'child_name', 'age_group', 'gender', 'birth_month', 'relation'];
    foreach ($required_fields as $field) {
        if (empty($_POST[$field])) {
            $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
            wp_redirect(home_url('/personalize-book?error=missing_fields&product_id=' . $product_id));
            exit;
        }
    }

    $product_id = intval($_POST['product_id']);
    $product = wc_get_product($product_id);

    if (!$product) {
        wp_redirect(home_url('/personalize-book?error=invalid_input&product_id=' . $product_id));
        exit;
    }

    $child_image_url = '';

    if (!empty($_FILES['child_image']['tmp_name'])) {
        require_once(ABSPATH . 'wp-admin/includes/file.php');
        $uploaded = media_handle_upload('child_image', 0);

        if (is_wp_error($uploaded)) {
            wp_redirect(home_url('/personalize-book?error=image_upload&product_id=' . $product_id));
            exit;
        } else {
            $child_image_url = wp_get_attachment_url($uploaded);
        }
    }

    $_SESSION['bp_personalization'] = [
        'product_id'      => $product_id,
        'child_name'      => sanitize_text_field($_POST['child_name']),
        'age_group'       => sanitize_text_field($_POST['age_group']),
        'gender'          => sanitize_text_field($_POST['gender']),
        'birth_month'     => sanitize_text_field($_POST['birth_month']),
        'relation'        => sanitize_text_field($_POST['relation']),
        'child_image_url' => esc_url_raw($child_image_url),
    ];

    // Redirect to face swap handler (or preview page)
    wp_safe_redirect(admin_url('admin-post.php?action=bp_handle_face_swap'));
    exit;
}

Did I leave any white space anywhere?

Search by product attributes WooCommerce/WordPress

• I have a product.
• In the product card there are 2 attributes (with different id’s), with two different prices.
• In wp_postmeta the product has 2(!!!) meta_key == _price.

Accordingly, the search works on the 1st price.
How to make the search work on the 2nd price?

Attribute id of the 2nd price is ‘rentfortheevent’ (‘pa_rentfortheevent’ in the attributes table in database).

Right now the search by price is done like this:

function set_price($q)
  {
   $min_price = $_GET["min_price"];
   $max_price = $_GET["max_price"];

   $meta_query = $q -> get("meta_query");

   if ($min_price)
    {
     $meta_query[] = 
      [
       "key"     => "_price",
       "value"   => $min_price,
       "type"    => "NUMERIC",
       "compare" => ">=",
      ];
    }

   if ($max_price)
    {
     $meta_query[] = 
      [
       "key"     => "_price",
       "value"   => $max_price,
       "type"    => "NUMERIC",
       "compare" => "<=",
      ];
    }

   $q -> set("meta_query", $meta_query);
  }

 add_action("woocommerce_product_query", "set_price");
}

wp_postdata screenshot with two _price

I required three levels of Admin in Laravel 11 [closed]

I required three levels of admin in laravel 11

  1. Supar Admin
  2. Office Admin
  3. Admin Staffs

The admin table is like below

| id | username    | password | hierarchy |
|----|-------------|----------|-----------|
| 1  | superadmin  | ******** | 1         |
| 2  | officeadmin | ******** | 2         |
| 3  | adminstaff  | ******** | 3         |

In AdminController -> login function I wrote:

public function login(Request $request){
    if($request->isMethod('post')){
        $data=$request->all();

        $rules = [
            'email'=> 'required|email|max:255',
            'password'=>'required',
        ];

        $customMessages = [
            'email.required'=> 'Email is Required',
            'email.email'=>'Valid Email is Required',
            'password.required'=>'Password is Required',
        ];

        $this->validate($request,$rules,$customMessages);
        
        if(Auth::guard('admin')->attempt(['email'=>$data['email'],'password'=>$data['password']])){
            return redirect('admin/dashboard');
        }
        else{
            Session::flash('error_message','Invalid Email or Password!');
            return redirect()->back();
        }
    }
    return view('admin.admin_login');
}

Now after successful login, I wish that

  • For hierarchy 1, url goes to ‘admin/dashboard’
  • For hierarchy 2, url goes to ‘office/dashboard’
  • For hierarchy 3, url goes to ‘staff/dashboard’

Please guide me how to do that.

Thanks & Regards,

$_SERVER[‘SCRIPT_NAME’] and $_SERVER[‘PHP_SELF’] returns index.php although the script is run on other page(s) [duplicate]

I am building an HTML form with PHP validation inside WordPress. Initially I built it on index.php and had no issues. But upon moving the form to another page (/form.php) the problem arise, since I want the form to be run and error handled on the same page (equivalent to action="").

I have tried with $_SERVER['PHP_SELF']:

<form method="POST" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']);?>">
    <input name="test">
    <button type="submit" name="submit">Send</button>
</form>

with $_SERVER['SCRIPT_NAME']:

<form method="POST" action="<?php echo htmlspecialchars($_SERVER['SCRIPT_NAME']); ?>">
    <input name="test">
    <button type="submit" name="submit">Send</button>
</form>

and with $_SERVER['SCRIPT_FILENAME']:

<form method="POST" action="<?php echo htmlspecialchars($_SERVER['SCRIPT_FILENAME']); ?>">
    <input name="test">
    <button type="submit" name="submit">Send</button>
</form>

but the form directs me to index.php although form.php (or the like) is excepted.

When checking the output of var_dump($_SERVER) on the page, these values are outputed on the screen:

["PHP_SELF"]=> string(24) "/root-folder-name/index.php"
["SCRIPT_NAME"]=> string(24) "/root-folder-name/index.php"
["SCRIPT_FILENAME"]=> string(40) "[...]/root-folder-name/index.php"

What am I missing?


This one returns the path of the current document:
["REQUEST_URI"]=> string(20) "/root-folder-name/form/".

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?