Can you adjust the image size within the app block of admin ui extensions?

I can’t change the size of the image, it just show its orignal size. I need something like thumbnail but no thumbnail component for admin extensions.

Hi @donsuper

Can you share a screenshot of where you’re adding this in? It’s possible the size of the image is intentionally restricted to ensure admin design patterns remain consistent.

I can get a thumbnail sized picture now, but don’t know it is the correct way to do it. And I can’t get the inlineAlignment=“space-between” working, the left and right column won’t push each other to opposite side to the end.

import { useState, useEffect, useCallback, default as React } from ‘react’;
import {
reactExtension,
useApi,
AdminAction,
BlockStack,
Text,
Badge,
InlineStack,
Image,
Divider,
Button,
Box,
Banner,
NumberField,
} from ‘@shopify/ui-extensions-react/admin’;

const TARGET = ‘admin.order-details.action.render’;

export default reactExtension(TARGET, () => );

function App() {
const { close, data } = useApi();
const orderId = data.selected[0].id;

const [lineItems, setLineItems] = useState();
const [pickedQuantities, setPickedQuantities] = useState({});
const [initialPickedQuantities, setInitialPickedQuantities] = useState({});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);

const METADATA_NAMESPACE = “pro_picklist_creater_app”;
const METADATA_KEY = “picked_quantities”;

useEffect(() => {
if (!orderId) {
setLoading(false);
return;
}

async function fetchData() {
  setLoading(true);
  const response = await fetch("shopify:admin/api/graphql.json", {
    method: "POST",
    body: JSON.stringify({
      query: `query GetOrderDetails($id: ID!) {
        order(id: $id) {
          pickedQuantities: metafield(namespace: "${METADATA_NAMESPACE}", key: "${METADATA_KEY}") {
            value
          }
          lineItems(first: 50) {
            edges {
              node {
                id
                name
                quantity
                fulfillableQuantity
                variant { image { url } sku }
                product { featuredImage { url } }
              }
            }
          }
        }
      }`,
      variables: { id: orderId },
    }),
  });

  if (!response.ok) {
    setError('Failed to fetch data.');
    setLoading(false);
    return;
  }

  const responseData = await response.json();
  if (responseData.errors) {
    setError("GraphQL query failed.");
    setLoading(false);
    return;
  }
  
  const orderData = responseData.data?.order;
  if (orderData) {
    const parsedPickedQuantities = JSON.parse(orderData.pickedQuantities?.value || '{}');
    setPickedQuantities(parsedPickedQuantities);
    setInitialPickedQuantities(parsedPickedQuantities);
    
    const unfulfilledItems = orderData.lineItems.edges
      .map(edge => edge.node)
      .filter(item => item.fulfillableQuantity > 0);
    setLineItems(unfulfilledItems);
  }
  setLoading(false);
}

fetchData();

}, [orderId]);

const handleQuantityChange = useCallback((lineItemId, value) => {
setPickedQuantities((prev) => ({
…prev,
[lineItemId]: value,
}));
}, );

const handleCancelChanges = useCallback(() => {
setPickedQuantities(initialPickedQuantities);
}, [initialPickedQuantities]);

const handleSaveChanges = useCallback(async () => {
setSaving(true);
setError(null);

const response = await fetch("shopify:admin/api/graphql.json", {
  method: "POST",
  body: JSON.stringify({
    query: `mutation SetMetafields($metafield: MetafieldsSetInput!) {
      metafieldsSet(metafields: [$metafield]) {
        userErrors { field, message }
      }
    }`,
    variables: {
      metafield: {
        ownerId: orderId,
        namespace: METADATA_NAMESPACE,
        key: METADATA_KEY,
        type: "json",
        value: JSON.stringify(pickedQuantities),
      },
    },
  }),
});

if (response.ok) {
  const responseData = await response.json();
  if (responseData.data?.metafieldsSet?.userErrors?.length) {
    setError("Failed to save changes.");
  } else {
    setInitialPickedQuantities(pickedQuantities);
  }
} else {
  setError("Failed to save changes.");
}
setSaving(false);

}, [orderId, pickedQuantities]);

const initialKeys = Object.keys(initialPickedQuantities);
const currentKeys = Object.keys(pickedQuantities);
let hasChanges = initialKeys.length !== currentKeys.length;
if (!hasChanges) {
for (const key of initialKeys) {
if ((initialPickedQuantities[key] || 0) !== (pickedQuantities[key] || 0)) {
hasChanges = true;
break;
}
}
}

const unpickedQty = lineItems.reduce((total, item) => {
const picked = pickedQuantities[item.id] || 0;
return total + (item.quantity - picked);
}, 0);

const allItemsPicked = unpickedQty <= 0 && lineItems.length > 0;

let bannerTitle = ${unpickedQty} total unit(s) need to be picked.;
let bannerTone = ‘warning’;
if (allItemsPicked) {
bannerTitle = ‘All items picked’;
bannerTone = ‘success’;
}

return (
<AdminAction
title=“Picklist Status”
primaryAction={
hasChanges ? (

Confirm Changes

) : (
<Button variant=“primary” onPress={() => close()}>
Close

)
}
secondaryAction={

Cancel

}
>
{loading && Loading…}
{error && {error}}
{!loading && !error && (

<Banner title={bannerTitle} tone={hasChanges ? ‘critical’ : bannerTone} />

{lineItems.length > 0 ? (
lineItems.map((item, index) => {
const { name, quantity, variant, product } = item;
const pickedQty = pickedQuantities[item.id] || 0;

            let badgeTone = 'warning';
            let badgeText = 'Not Picked';
            if (pickedQty > 0 && pickedQty < quantity) {
              badgeTone = 'info';
              badgeText = 'Partial';
            } else if (pickedQty >= quantity) {
              badgeTone = 'success';
              badgeText = 'Picked';
            }

            const originalUrl = variant?.image?.url || product?.featuredImage?.url;
            let thumbnailUrl = '';
            if (originalUrl) {
              const parts = originalUrl.split('?');
              const urlWithoutParams = parts[0];
              const params = parts.length > 1 ? `?${parts[1]}` : '';
              const extensionIndex = urlWithoutParams.lastIndexOf('.');
              if (extensionIndex !== -1) {
                const preExtension = urlWithoutParams.substring(0, extensionIndex);
                const extension = urlWithoutParams.substring(extensionIndex);
                thumbnailUrl = `${preExtension}_50x50${extension}${params}`;
              }
            }

            return (
              <React.Fragment key={item.id}>
                {index > 0 && <Divider />}
                <Box padding="base">
                  <InlineStack blockAlignment="center" inlineAlignment="space-between" inlineSize="100%">
                    <InlineStack gap="base" blockAlignment="center">
                      {thumbnailUrl ? (
                        <Image source={thumbnailUrl} alt={name} />
                      ) : (
                        <Image source="https://cdn.shopify.com/s/files/1/0533/2089/files/placeholder-images-image_small.png?format=webp&v=1530129081" />
                      )}
                      <BlockStack gap="none">
                        <Text fontWeight="bold">{name}</Text>
                        {variant?.sku && <Text>SKU: {variant.sku}</Text>}
                      </BlockStack>
                    </InlineStack>

                    <InlineStack gap="base" blockAlignment="center">
                      <Box maxInlineSize={85}>
                        <NumberField 
                            labelHidden
                            value={pickedQty}
                            onChange={(newValue) => handleQuantityChange(item.id, newValue)}
                            min={0}
                            max={quantity}
                            suffix={`/${quantity}`}
                        />
                      </Box>
                      <Badge tone={badgeTone}>{badgeText}</Badge>
                    </InlineStack>
                    
                  </InlineStack>
                </Box>
              </React.Fragment>
            );
          })
        ) : (
          <Box padding="base">
            <Text>All items in this order have been fulfilled.</Text>
          </Box>
        )}
      </BlockStack>
    </BlockStack>
  )}
</AdminAction>

);
}