InícioDocs para devsImagens de ator: portrait e token
Exemplos por kind 4 min de leitura

Imagens de ator: portrait e token

Duas funções completas de montagem com seletor File e a operação multipart real de host.call.

Estes dois exemplos usam host.call("actor.image.upload", payload), uma operação do SDK de bloco. Eles não usam api.actors.update para transmitir arquivos. O retorno é {url} e o servidor valida campanha, edição do ator e imagem.

Prepare o pacote mínimo, um ator fictício e uma imagem original PNG/JPEG/WebP. Registre cada função na superfície actor.sheet: register("actor.sheet", mountPortraitUpload) ou register("actor.sheet", mountTokenUpload). O contexto precisa conter actorId e canEdit; o usuário precisa ser GM ou ter permissão de edição do ator. O controle fica desabilitado quando canEdit não é verdadeiro; o servidor continua sendo a autoridade.

Copie uma função inteira para main.js. Não copie o descritor de arquivo do JSON Schema para o campo file: no navegador ele recebe o File real do seletor. O bridge produz multipart e metadados. Não use base64.

kind: "portrait": Enviar um retrato

Uso: trocar o retrato do ator exibido na ficha.

javascript
export function mountPortraitUpload({ root, context, host, signal }) {
  const doc = root.ownerDocument;
  const label = doc.createElement("label");
  label.textContent = "Enviar um retrato";
  const input = doc.createElement("input");
  input.type = "file";
  input.accept = "image/png,image/jpeg,image/webp";
  const status = doc.createElement("output");
  status.setAttribute("aria-live", "polite");
  const preview = doc.createElement("img");
  preview.alt = "Imagem enviada";
  preview.width = 128;
  preview.height = 128;
  preview.hidden = true;
  label.append(input);
  root.append(label, status, preview);
  input.disabled = context.canEdit !== true;

  input.addEventListener("change", async () => {
    const file = input.files?.[0];
    if (!file || signal.aborted) return;
    input.disabled = true;
    status.textContent = "Enviando…";
    try {
      const result = await host.call("actor.image.upload", {
        id: context.actorId,
        kind: "portrait",
        file,
      }, { signal });
      if (signal.aborted) return;
      preview.src = result.url;
      preview.hidden = false;
      status.textContent = "Imagem salva.";
    } catch (error) {
      if (!signal.aborted) status.textContent = "Falha: " + error.code;
    } finally {
      if (!signal.aborted) input.disabled = context.canEdit !== true;
    }
  }, { signal });
}

Resultado esperado: o preview usa a URL autorizada retornada e o ator conserva a nova imagem portrait após reabrir a ficha.

Valide: envie uma imagem pequena, reabra a ficha e confira a imagem. Repita com jogador autorizado e somente leitura. Feche durante o envio e verifique que a interface encerrada não recebe atualizações.

kind: "token": Enviar a imagem do token

Uso: trocar a imagem de token associada ao ator; isto não altera posição, escala ou tipo de vínculo de tokens já colocados.

javascript
export function mountTokenUpload({ root, context, host, signal }) {
  const doc = root.ownerDocument;
  const label = doc.createElement("label");
  label.textContent = "Enviar a imagem do token";
  const input = doc.createElement("input");
  input.type = "file";
  input.accept = "image/png,image/jpeg,image/webp";
  const status = doc.createElement("output");
  status.setAttribute("aria-live", "polite");
  const preview = doc.createElement("img");
  preview.alt = "Imagem enviada";
  preview.width = 128;
  preview.height = 128;
  preview.hidden = true;
  label.append(input);
  root.append(label, status, preview);
  input.disabled = context.canEdit !== true;

  input.addEventListener("change", async () => {
    const file = input.files?.[0];
    if (!file || signal.aborted) return;
    input.disabled = true;
    status.textContent = "Enviando…";
    try {
      const result = await host.call("actor.image.upload", {
        id: context.actorId,
        kind: "token",
        file,
      }, { signal });
      if (signal.aborted) return;
      preview.src = result.url;
      preview.hidden = false;
      status.textContent = "Imagem salva.";
    } catch (error) {
      if (!signal.aborted) status.textContent = "Falha: " + error.code;
    } finally {
      if (!signal.aborted) input.disabled = context.canEdit !== true;
    }
  }, { signal });
}

Resultado esperado: o preview usa a URL autorizada retornada e o ator conserva a nova imagem token após reabrir a ficha.

Valide: envie uma imagem pequena, reabra a ficha e confira a imagem. Repita com jogador autorizado e somente leitura. Feche durante o envio e verifique que a interface encerrada não recebe atualizações.

Limites, erros e ciclo de vida

O servidor aceita PNG, JPEG ou WebP com até 10.000.000 bytes e 25.000.000 pixels. Ele normaliza a orientação e salva PNG. O atributo accept apenas orienta o seletor; não substitui essa validação.

Ator inacessível pode retornar not_found ou permission_denied; imagem inválida retorna invalid_data; falha de transporte pode retornar unavailable. O fechamento cancela o contexto: ignore atualizações de DOM após signal.aborted. Um envio já confirmado pelo servidor permanece salvo mesmo que a interface feche. O listener usa {signal} e é removido na desmontagem. Use api.actors.get(context.actorId) novamente antes de uma edição posterior que dependa da versão do ator.

Como usar a IA para criar um seletor de imagem

text
Confira o schema actor.image.upload e browser-bridge.js na release alvo. Adapte mountTokenUpload para exibir progresso acessível usando a opção onProgress de host.call. Preserve File multipart, permissões nativas, signal, limpeza e textContent. Não invente um endpoint ou grave URL do usuário diretamente no ator. Mostre testes de imagem inválida, fechamento durante envio e jogador somente leitura.