Skip to content

Commit

Permalink
feat: render embedded images in credential subject (#351)
Browse files Browse the repository at this point in the history
* refactor: extract credential subject into file

* feat: render `image` instead of displaying it as text

* refactor: use `portrait` instead of `image`

* style: fix clippy issue

* fix: align field styling with UI designs

* feat: add renderer for simple text fields, distinguish based on credential type

* refactor: add renderer, set white background for all logos

* fix: enable feature `webview-data-url`

* security: allow `data:` image source through CSP

* fix: typo

* fix: apply `background` and `background-alt` correctly

* refactor: adjust top nav bar

* fix: add white background to issuer logo

* fix: contain image within fixed height

* test: add embedded image to test fixture
  • Loading branch information
daniel-mader committed Sep 18, 2024
1 parent 42573bb commit 5f29c21
Show file tree
Hide file tree
Showing 14 changed files with 69 additions and 25 deletions.
20 changes: 20 additions & 0 deletions identity-wallet/resources/ferris-drivers-license-payload.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
subject::subject,
};

use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use jsonwebtoken::Algorithm;
use lazy_static::lazy_static;
use log::info;
Expand All @@ -33,9 +34,10 @@ lazy_static! {
record.display_credential.display_name = "PersonalInformation".to_string();
record
};
pub static ref DRIVERS_LICENSE_CREDENTIAL_PAYLOAD: String = URL_SAFE_NO_PAD.encode(include_bytes!("../../../../resources/ferris-drivers-license-payload.json"));
pub static ref DRIVERS_LICENSE_CREDENTIAL: VerifiableCredentialRecord = {
let mut record = VerifiableCredentialRecord::try_from(
json!("eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2toUDQzTENTWGFqM1NRQm92eTF1RTJuWHZTQm5SUFdaMndoUExxblo4UGdEI3o2TWtraFA0M0xDU1hhajNTUUJvdnkxdUUyblh2U0JuUlBXWjJ3aFBMcW5aOFBnRCJ9.eyJpc3MiOiJodHRwOi8vMTkyLjE2OC4xLjEyNzo5MDkwLyIsInN1YiI6ImRpZDprZXk6ejZNa2cxWFhHVXFma2hBS1Uxa1ZkMVBtdzZVRWoxdnhpTGoxeGM5MU1CejVvd05ZIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjAsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRyaXZlckxpY2Vuc2VDcmVkZW50aWFsIl0sImlzc3VlciI6Imh0dHA6Ly8xOTIuMTY4LjEuMTI3OjkwOTAvIiwiaXNzdWFuY2VEYXRlIjoiMjAwNC0wMi0wOFQwODoxNDowOFoiLCJleHBpcmF0aW9uRGF0ZSI6IjIwMjctMDgtMTVUMjM6NTk6NTlaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6a2V5Ono2TWtnMVhYR1VxZmtoQUtVMWtWZDFQbXc2VUVqMXZ4aUxqMXhjOTFNQno1b3dOWSIsImxpY2Vuc2VDbGFzcyI6IkNsYXNzIEMiLCJpc3N1ZWRCeSI6IkNhbGlmb3JuaWEiLCJ2YWxpZGl0eSI6IlZhbGlkIn19fQ.OZCcZt5JTJcBhoLPIyrQuvZuc2dnVN65f8GvKQ3earAzJEgGMA9ZjKRNHEjI73wLwvG5MJBN7Zs_rWiNLEZ5Dg"),
json!(format!("eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2toUDQzTENTWGFqM1NRQm92eTF1RTJuWHZTQm5SUFdaMndoUExxblo4UGdEI3o2TWtraFA0M0xDU1hhajNTUUJvdnkxdUUyblh2U0JuUlBXWjJ3aFBMcW5aOFBnRCJ9.{}.OZCcZt5JTJcBhoLPIyrQuvZuc2dnVN65f8GvKQ3earAzJEgGMA9ZjKRNHEjI73wLwvG5MJBN7Zs_rWiNLEZ5Dg", DRIVERS_LICENSE_CREDENTIAL_PAYLOAD.clone())),
).unwrap();
record.display_credential.display_name = "DriverLicenseCredential".to_string();
record
Expand Down
4 changes: 2 additions & 2 deletions unime/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"$schema": "../../node_modules/@tauri-apps/cli/schema.json",
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"productName": "UniMe Beta",
"version": "0.6.8",
"identifier": "com.impierce.unime",
"app": {
"security": {
"csp": "default-src 'self' ipc: http://ipc.localhost; style-src 'unsafe-inline' 'self'; img-src 'self' asset: http://asset.localhost",
"csp": "default-src 'self' ipc: http://ipc.localhost; style-src 'unsafe-inline' 'self'; img-src 'self' asset: http://asset.localhost data:",
"assetProtocol": {
"enable": true,
"scope": [
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion unime/src/lib/components/navigation/TopNavBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
$$props.class,
)}
>
<button class="z-30 -ml-2 rounded-full p-2 disabled:opacity-25" on:click={() => dispatch('back')} {disabled}>
<button class="z-30 -ml-4 rounded-full p-2 disabled:opacity-25" on:click={() => dispatch('back')} {disabled}>
<CaretLeftBoldIcon class="h-5 w-5" />
</button>
{#if title}
Expand Down
2 changes: 1 addition & 1 deletion unime/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
let resetDragonProfile = true;
// Tailwind considers app to be in dark theme if the class `dark` is present.
// A user can chooser between 3 themes: `light`, `dark`, and `system`.
// A user can choose between 3 themes: `light`, `dark`, and `system`.
// For `system` we need to check `prefers-color-scheme: dark` media query.
// `prefersColorSchemeDarkStore` monitors whether this media query is applied.
Expand Down
4 changes: 2 additions & 2 deletions unime/src/routes/credentials/TopNavBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
export let title: string;
</script>

<div class="relative grid h-[48px] place-items-center px-4 text-sm font-medium">
<button class="absolute left-4 top-1/2 -translate-y-1/2 transform" on:click={() => dispatch('back')}>
<div class="relative grid h-[50px] place-items-center px-4 text-[13px]/[24px] font-medium">
<button class="absolute left-2 top-1/2 -translate-y-1/2 transform rounded-full p-2" on:click={() => dispatch('back')}>
<CaretLeftBoldIcon class="h-5 w-5" />
</button>
<span>{title}</span>
Expand Down
2 changes: 1 addition & 1 deletion unime/src/routes/credentials/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</script>

{#if credential}
<div class="flex flex-col gap-7 px-4 pb-7">
<div class="flex flex-col gap-7 bg-background-alt px-4 pb-7">
<CredentialHeader {credential}>
<h1 class="text-center font-semibold">
{credential.data?.name ?? credential.display_name}
Expand Down
9 changes: 5 additions & 4 deletions unime/src/routes/credentials/[id]/CredentialHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
</script>

<!-- Stretch over parent horizontal padding with negative margins. -->
<div class="relative -mx-4 flex flex-col items-center gap-4 bg-background-alt py-5">
<div class="grid h-40 w-40 place-items-center rounded-xl bg-background">
<div class="relative -mx-4 flex flex-col items-center gap-4 bg-background py-5">
<!-- Background is always white since most logos are designed for light backgrounds -->
<div class="grid h-40 w-40 place-items-center rounded-xl bg-white">
{#if credentialLogoUrl}
<!-- Fit image of unknown dimensions into availalbe space with `contain` (not `cover`). -->
<!-- Fit image of unknown dimensions into available space with `contain` (not `cover`). -->
<img src={credentialLogoUrl} alt="Credential logo" class="h-32 w-32 object-contain" />
{:else}
<CertificateLightIcon class="h-10 w-10" />
<CertificateLightIcon class="h-10 w-10 text-text-alt" />
{/if}
</div>

Expand Down
9 changes: 5 additions & 4 deletions unime/src/routes/credentials/[id]/CredentialOverview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
});
</script>

<div class="grid grid-cols-2 gap-4 text-xs font-medium">
<div class="grid grid-cols-2 gap-4 bg-background-alt text-xs font-medium">
<div class="flex flex-col items-center gap-1">
<div>{$LL.CREDENTIAL.DETAILS.VALID()}</div>
<div class="grid h-20 place-items-center self-stretch rounded-xl bg-background-alt py-5 text-text-alt">
<div class="grid h-20 place-items-center self-stretch rounded-xl bg-background py-5 text-text-alt">
<SealCheckRegularIcon class="h-7 w-7" />
</div>
{#if credential.data.issuanceDate}
Expand All @@ -47,10 +47,11 @@
this={credential.connection_id ? 'button' : 'div'}
on:click={credential.connection_id ? () => goto(`/activity/connection/${credential.connection_id}`) : undefined}
role={credential.connection_id ? 'button' : undefined}
class="grid h-20 place-items-center self-stretch rounded-xl bg-background-alt py-5 text-text-alt"
class="grid h-20 place-items-center self-stretch rounded-xl bg-background text-text-alt"
>
{#if issuerLogoUrl}
<img src={issuerLogoUrl} alt="Issuer logo" class="h-10 w-10 object-contain" />
<!-- Background is always white since most logos are designed for light backgrounds -->
<img src={issuerLogoUrl} alt="Issuer logo" class="h-12 w-12 rounded-xl bg-white object-contain p-1.5" />
{:else}
<BankLightIcon class="h-7 w-7" />
{/if}
Expand Down
13 changes: 13 additions & 0 deletions unime/src/routes/credentials/[id]/DataUrlImageRenderer.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script lang="ts">
export let key: string;
export let dataUrl: string;
</script>

<!--
@component
This renderer for `credentialSubject` fields renders an embedded image for a given [Data URL](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data).
-->
<div class="flex flex-col items-start rounded-xl bg-background px-4 py-3 text-[13px]/[24px]">
<h2 class="w-full font-medium text-text-alt">{key}</h2>
<img src={dataUrl} alt="embedded" class="my-1 h-32 rounded-md object-contain" />
</div>
14 changes: 10 additions & 4 deletions unime/src/routes/credentials/[id]/DefaultRenderer.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script lang="ts">
import type { DisplayCredential } from '@bindings/credentials/DisplayCredential';
import DataUrlImageRenderer from './DataUrlImageRenderer.svelte';
export let credential: DisplayCredential;
// If you add a field, add a comment why that field should be hidden.
Expand All @@ -14,10 +16,14 @@
{#if fields}
<div class="flex flex-col gap-4">
{#each fields as field}
<div class="rounded-xl bg-background-alt px-4 py-3 text-[13px]/[24px]">
<h2 class="font-medium text-text-alt">{field}</h2>
<p class="overflow-x-auto">{credential.data.credentialSubject[field]}</p>
</div>
{#if credential.data.credentialSubject[field].startsWith('data:image/')}
<DataUrlImageRenderer key={field} dataUrl={credential.data.credentialSubject[field]} />
{:else}
<div class="rounded-xl bg-background px-4 py-3 text-[13px]/[24px]">
<h2 class="font-medium text-text-alt">{field}</h2>
<p class="overflow-x-auto">{credential.data.credentialSubject[field]}</p>
</div>
{/if}
{/each}
</div>
{/if}
Expand Down
4 changes: 2 additions & 2 deletions unime/src/routes/credentials/[id]/OpenBadgeRenderer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<!-- Render `credentialSubject.achievement`. -->
<div class="flex flex-col gap-4">
{#if credential.data.credentialSubject?.achievement?.description}
<div class="prose prose-sm rounded-xl bg-background-alt p-4 dark:prose-invert">
<div class="prose prose-sm rounded-xl bg-background p-4 dark:prose-invert">
<h2>{$LL.CREDENTIAL.DETAILS.DESCRIPTION()}</h2>
<!-- TODO Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
Expand All @@ -23,7 +23,7 @@
{/if}

{#if credential.data.credentialSubject?.achievement?.criteria?.narrative}
<div class="prose prose-sm rounded-xl bg-background-alt p-4 dark:prose-invert">
<div class="prose prose-sm rounded-xl bg-background p-4 dark:prose-invert">
<h2>{$LL.CREDENTIAL.DETAILS.CONTENTS()}</h2>
<!-- TODO Review marked vs. markdown-it and security risks. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
Expand Down
2 changes: 1 addition & 1 deletion unime/src/routes/credentials/[id]/TextFieldRenderer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
export let value: string;
</script>

<div class="rounded-xl bg-background-alt px-4 py-3 text-[13px]/[24px]">
<div class="rounded-xl bg-background px-4 py-3 text-[13px]/[24px]">
<h2 class="font-medium text-text-alt">{key}</h2>
<p class="overflow-x-auto">{value}</p>
</div>

0 comments on commit 5f29c21

Please sign in to comment.