Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a8bb1a7
feat(form): add image field UI
holtvogt Sep 28, 2026
38def6a
feat(form): confirm image field removal
holtvogt Sep 29, 2026
434d8a7
feat(form): render media fields with asset sources
holtvogt Sep 29, 2026
9b7e684
chore(asset): update dialog text
holtvogt Sep 29, 2026
67ef05a
fix(asset): correct punctuation
holtvogt Sep 29, 2026
317b72a
feat(form): select images from AEM Assets
holtvogt Oct 1, 2026
3da08dd
fix(aem-dialog): adjust width and height
holtvogt Oct 1, 2026
a60e1d5
chore(deps): patch da-sc-sdk bundle for media fields
holtvogt Oct 1, 2026
078821a
fix(form): show a neutral image preview while loading
holtvogt Oct 2, 2026
a1100ad
refactor(button): remove negative variant
holtvogt Oct 5, 2026
e3fd208
refactor(utils): share memoized live preview login
holtvogt Oct 6, 2026
c9c1bc4
feat(aem-assets): export getMimetype
holtvogt Oct 6, 2026
a3f4f30
feat(media): add shared media upload policy
holtvogt Oct 6, 2026
2bc2157
feat(form): support generic file fields
holtvogt Oct 6, 2026
e93f415
chore(deps): bundle contentMediaType support
holtvogt Oct 6, 2026
a59e930
Merge branch 'main' into feat-sc-asset-picker
holtvogt Oct 6, 2026
d36aeda
refactor(form): drop contentMediaType from fields
holtvogt Oct 7, 2026
1252b2f
fix(form): log into media previews with nx2 IMS
holtvogt Oct 7, 2026
df31a6c
feat(form): show file type badge in media previews
holtvogt Oct 7, 2026
564abdf
feat(form): keep file extension visible in long names
holtvogt Oct 7, 2026
f1d362d
refactor(aem-selector): simplify token retrieval
holtvogt Oct 7, 2026
c710390
chore(deps): bundle da-sc-sdk media image support
holtvogt Oct 8, 2026
870365d
fix(form): save image fields as images so EDS delivers them
holtvogt Oct 8, 2026
42b2d06
Merge branch 'adobe:main' into feat-sc-asset-picker
holtvogt Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 59 additions & 5 deletions nx/blocks/form/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { LitElement, html, nothing } from 'da-lit';
import { createEngine } from '../../deps/da-sc-sdk/dist/index.js';
import { loadFormContext } from './utils/context.js';
import { attachPersistence } from './utils/persistence.js';
import { loadStyle, hashChange } from '../../../nx2/utils/utils.js';
import { createAssetSources } from './utils/assets.js';
import {
ensurePreviewLogin, getLivePreviewUrl, loadStyle, hashChange,
} from '../../../nx2/utils/utils.js';

import './views/editor.js';
import './views/sidebar.js';
Expand All @@ -19,16 +22,17 @@ const EL_NAME = 'nx-form';

// Derive the legacy `details` shape the form utils expect from the EW-supplied
// `ctx`. `ctx.path` is the full `org/site/path` locator; the DA source is the
// `.html` document at that path.
// `.html` document at that path. `parent` and `name` match the upload context Canvas uses.
function detailsFromCtx(ctx) {
if (!ctx?.org || !ctx?.repo || !ctx?.path) return null;
const raw = `/${String(ctx.path).replace(/^\/+/, '')}`;
const fullpath = raw.toLowerCase().endsWith('.html') ? raw : `${raw}.html`;
const name = fullpath.slice(fullpath.lastIndexOf('/') + 1, -'.html'.length);
const separator = fullpath.lastIndexOf('/');
return {
owner: ctx.org,
repo: ctx.repo,
name,
parent: fullpath.slice(0, separator),
name: fullpath.slice(separator + 1, -'.html'.length),
fullpath,
sourceUrl: fullpath,
};
Expand All @@ -41,13 +45,26 @@ function ctxFromHashState(state) {
return { org: state.org, repo: state.site, path: state.fullpath.slice(1) };
}

async function loadAemRepoConfig({ owner, repo }) {
try {
const { getRepositoryConfig } = await import('../../../nx2/utils/aem-assets/repository-config.js');
return { repoConfig: await getRepositoryConfig(owner, repo) };
} catch (error) {
return { error };
}
}

class Form extends LitElement {
static properties = {
ctx: { attribute: false },

_context: { state: true },
_state: { state: true },
_nav: { state: true },
_pendingSchemaId: { state: true },

_assetSources: { state: true },
_previewOrigin: { state: true },
};

// Reactive properties (declared in static properties) must NOT have class-
Expand Down Expand Up @@ -119,7 +136,10 @@ class Form extends LitElement {
this._state = this._editor.getState();
// Attach AFTER load so the loaded document is the persistence's baseline —
// mutations after this point trigger saves; the load itself does not.
this._persistence = attachPersistence(this._editor, { path: this._details?.fullpath });
this._persistence = attachPersistence(this._editor, {
path: this._details?.fullpath,
schema,
});
this._nav = { pointer: '/data', origin: null, seq: 0 };
}

Expand All @@ -146,6 +166,10 @@ class Form extends LitElement {
this._editor = null;
this._persistence?.detach();
this._persistence = null;

this._assetSources = undefined;
this._previewOrigin = undefined;

this._details = detailsFromCtx(this.ctx);

if (!this._details) {
Expand All @@ -163,6 +187,34 @@ class Form extends LitElement {
if (context.status === 'ready') {
this._start({ schema: context.schema, json: context.json });
}

if (context.status === 'ready' || context.status === 'select-schema') {
await this._loadAssetContext(version);
}
}

// File sources and previews load independently, so a slow preview login never delays sources.
async _loadAssetContext(version) {
const { owner, repo } = this._details;
const isCurrent = () => version === this._loadVersion;

ensurePreviewLogin({ org: owner, repo }).then(() => {
if (isCurrent()) {
this._previewOrigin = getLivePreviewUrl({ org: owner, repo });
}
});

const { repoConfig, error } = await loadAemRepoConfig({ owner, repo });
if (!isCurrent()) {
return;
}

if (error) {
// eslint-disable-next-line no-console
console.warn('AEM Assets configuration unavailable; offering Upload only.', error);
}

this._assetSources = createAssetSources({ details: this._details, repoConfig, isCurrent });
}

_onPendingSchemaChange(e) {
Expand Down Expand Up @@ -359,6 +411,8 @@ class Form extends LitElement {
.state=${this._state}
.nav=${this._nav}
.onSelect=${this._onSelect}
.assetSources=${this._assetSources}
.previewOrigin=${this._previewOrigin}
></nx-editor>
<nx-preview .state=${this._state}></nx-preview>
</div>
Expand Down
129 changes: 129 additions & 0 deletions nx/blocks/form/fields/asset.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
:host {
--asset-preview-height: 194px;
--asset-row-height: 56px;
--asset-transparency-grid-size: 8px;
--asset-preview-fade: 130ms;
--asset-menu-icon-size: 16px;

display: block;
font-family: var(--s2-font-family, adobe-clean, "Source Sans Pro", system-ui, sans-serif);
}

/* Fixed rows in every state, so selecting, replacing or removing never shifts the page. */
.asset {
display: grid;
grid-template-rows: var(--asset-preview-height) var(--asset-row-height);
overflow: hidden;
border: 1px solid var(--s2-gray-300);
border-radius: var(--s2-corner-radius-500);
background: var(--s2-gray-25);
}

.has-error .asset {
border-color: var(--s2-red-900);
}

.asset-preview {
display: grid;
grid-template: minmax(0, 1fr) / minmax(0, 1fr);
place-items: center;
overflow: hidden;
background: var(--s2-gray-100);
}

/* The image covers the file type badge once it has loaded. */
.asset-preview > * {
grid-area: 1 / 1;
}

.asset-file-type {
padding: var(--s2-spacing-75) var(--s2-spacing-200);
border-radius: var(--s2-corner-radius-200);
background: var(--s2-gray-25);
color: var(--s2-gray-800);
font-size: var(--s2-body-size-s);
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}

.asset-preview img {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity var(--asset-preview-fade) ease-out;
background-color: var(--s2-gray-25);
background-image: conic-gradient(
var(--s2-gray-200) 90deg, transparent 90deg 180deg,
var(--s2-gray-200) 180deg 270deg, transparent 270deg
);
background-size: var(--asset-transparency-grid-size) var(--asset-transparency-grid-size);
}

.asset-preview img.is-loaded {
opacity: 1;
}

.asset-row {
box-sizing: border-box;
display: flex;
align-items: center;
gap: var(--s2-spacing-200);
min-width: 0;
padding: 0 var(--s2-spacing-200) 0 var(--s2-spacing-300);
border-top: 1px solid var(--s2-gray-200);
}

.asset-placeholder,
.asset-name {
flex: 1;
min-width: 0;
overflow: hidden;
font-size: var(--s2-body-size-s);
white-space: nowrap;
text-overflow: ellipsis;
}

.asset-name {
display: flex;
color: var(--s2-gray-900);
font-weight: 600;
}

.asset-name-stem {
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

.asset-name-extension {
flex: none;
}

.asset-placeholder {
color: var(--s2-gray-700);
}

.asset-actions {
display: flex;
flex-shrink: 0;
align-items: center;
gap: var(--s2-spacing-100);
}

.asset-source-trigger {
display: inline-flex;
align-items: center;
gap: var(--s2-spacing-75);
}

.asset-menu-chevron {
width: var(--asset-menu-icon-size);
height: var(--asset-menu-icon-size);
}

nx-dialog p {
margin: 0;
}
Loading
Loading