feat(studio): hand an export straight to a social network
EXPORT only ever wrote a file, and posting it meant leaving the studio, finding the file and uploading it again. The dialog now offers FACEBOOK, INSTAGRAM, REDDIT and X beside the sizes, and the render goes to the network instead of the download folder. What the browser can do decides how, since a page may not touch another origin's composer: a phone's share sheet opens the target app with the picture already attached, a desktop gets the picture on the clipboard and the site in a new tab for one paste, and an origin with no clipboard API downloads the file beside the open site. The line under the row says so before the click.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useI18n } from '../i18n/I18nProvider';
|
||||
import { SHARE_SITES, type ShareSite } from '../engine/share';
|
||||
|
||||
// Export size. The photo on the stage has a longest edge; the three fixed
|
||||
// answers name the sizes people ask for by name (2048/4096), the fourth takes a
|
||||
@@ -12,6 +13,7 @@ export function ExportModal({
|
||||
longest,
|
||||
maxDim,
|
||||
onPick,
|
||||
onShare,
|
||||
onClose,
|
||||
}: {
|
||||
/** Longest edge of the photo as it stands, crop included. */
|
||||
@@ -19,6 +21,10 @@ export function ExportModal({
|
||||
/** The tier's own ceiling, or null for none. */
|
||||
maxDim: number | null;
|
||||
onPick: (target: number | null) => void;
|
||||
/** Hand the file straight to a network. Always the photo unchanged: the
|
||||
sites resize to their own feed anyway, so an upscale here is pixels
|
||||
bought for nothing. */
|
||||
onShare: (site: ShareSite) => void;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
@@ -85,6 +91,28 @@ export function ExportModal({
|
||||
{t('export.cap', { px: maxDim })}
|
||||
</p>
|
||||
)}
|
||||
{/* Straight to a network, no file on the way. What happens next is the
|
||||
browser's to decide — see engine/share — so the line under the row
|
||||
says which of the three it will be before the click. */}
|
||||
<div className="export-share" data-key="export-share">
|
||||
<p className="hint">{t('export.share')}</p>
|
||||
<div className="export-share-row">
|
||||
{(Object.keys(SHARE_SITES) as ShareSite[]).map((site) => (
|
||||
<button
|
||||
key={site}
|
||||
type="button"
|
||||
className="btn ghost"
|
||||
data-key={`export-share-${site}`}
|
||||
onClick={() => onShare(site)}
|
||||
>
|
||||
{site === 'x' ? 'X' : site.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="hint" data-key="export-share-hint">
|
||||
{t('export.shareHint')}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" className="btn ghost" data-key="export-cancel" onClick={onClose}>
|
||||
{t('clear.cancel')}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user