UX: Wizard style preview improvements

* Render date on timeline
* Render timeline handle thicker and with a small gap at top
* Make sure body text does not overflow over timeline with
  bounding box calculation and dymanic font resizing
* Other minor improvements to spacing/sizing
This commit is contained in:
Martin Brennan 2024-12-05 17:15:13 +10:00
parent 2ac2c42b06
commit af5eed5005
No known key found for this signature in database
GPG Key ID: BD981EFEEC8F5675
3 changed files with 87 additions and 35 deletions

View File

@ -248,17 +248,20 @@ export default class PreviewBase extends Component {
ctx.drawImage(scaled[key], x, y, w, h); ctx.drawImage(scaled[key], x, y, w, h);
} }
get headerHeight() {
return this.height * 0.15;
}
drawFullHeader(colors, font, logo) { drawFullHeader(colors, font, logo) {
const { ctx } = this; const { ctx } = this;
const headerHeight = this.height * 0.15; drawHeader(ctx, colors, this.width, this.headerHeight);
drawHeader(ctx, colors, this.width, headerHeight);
const avatarSize = this.height * 0.1; const avatarSize = this.height * 0.1;
const headerMargin = headerHeight * 0.2; const headerMargin = this.headerHeight * 0.2;
if (logo) { if (logo) {
const logoHeight = headerHeight - headerMargin * 2; const logoHeight = this.headerHeight - headerMargin * 2;
const ratio = logoHeight / logo.height; const ratio = logoHeight / logo.height;
this.scaleImage( this.scaleImage(
@ -285,7 +288,7 @@ export default class PreviewBase extends Component {
colors.primary_low_mid || colors.primary_low_mid ||
darkLightDiff(colors.primary, colors.secondary, 45, 55); darkLightDiff(colors.primary, colors.secondary, 45, 55);
const pathScale = headerHeight / 1200; const pathScale = this.headerHeight / 1200;
// search icon SVG path // search icon SVG path
const searchIcon = new Path2D( const searchIcon = new Path2D(
"M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z" "M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"
@ -390,6 +393,29 @@ export default class PreviewBase extends Component {
x += categoriesSize * 0.6; x += categoriesSize * 0.6;
ctx.fillText("Top", x, headerHeight + headerMargin * 1.5 + fontSize); ctx.fillText("Top", x, headerHeight + headerMargin * 1.5 + fontSize);
} }
resizeTextLinesToFitRect(
textLines,
rectWidth,
ctx,
fontSize,
font,
renderCallback
) {
const maxLengthLine = textLines.reduce((a, b) =>
a.length > b.length ? a : b
);
let fontSizeDecreaseMultiplier = 1;
while (ctx.measureText(maxLengthLine).width > rectWidth) {
fontSizeDecreaseMultiplier -= 0.1;
ctx.font = `${fontSize * fontSizeDecreaseMultiplier}em '${font}'`;
}
for (let i = 0; i < textLines.length; i++) {
renderCallback(textLines[i], i);
}
}
} }
function loadImage(src) { function loadImage(src) {

View File

@ -7,8 +7,8 @@ import HomepagePreview from "./-homepage-preview";
import PreviewBaseComponent from "./-preview-base"; import PreviewBaseComponent from "./-preview-base";
export default class Index extends PreviewBaseComponent { export default class Index extends PreviewBaseComponent {
width = 628; width = 630;
height = 322; height = 380;
logo = null; logo = null;
avatar = null; avatar = null;
previewTopic = true; previewTopic = true;
@ -111,51 +111,60 @@ export default class Index extends PreviewBaseComponent {
} }
paint({ ctx, colors, font, headingFont, width, height }) { paint({ ctx, colors, font, headingFont, width, height }) {
const headerHeight = height * 0.3;
this.drawFullHeader(colors, headingFont, this.logo); this.drawFullHeader(colors, headingFont, this.logo);
const margin = 20; const margin = 20;
const avatarSize = height * 0.15; const avatarSize = height * 0.1 + 5;
const lineHeight = height / 14; const lineHeight = height / 14;
const leftHandTextGutter = margin + avatarSize + margin;
const timelineX = width * 0.86;
// Draw a fake topic // Draw a fake topic
this.scaleImage( this.scaleImage(
this.avatar, this.avatar,
margin, margin,
headerHeight + height * 0.09, this.headerHeight + height * 0.22,
avatarSize, avatarSize,
avatarSize avatarSize
); );
const titleFontSize = headerHeight / 55; const titleFontSize = this.headerHeight / 30;
// Topic title
ctx.beginPath(); ctx.beginPath();
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.font = `bold ${titleFontSize}em '${headingFont}'`; ctx.font = `bold ${titleFontSize}em '${headingFont}'`;
ctx.fillText(i18n("wizard.previews.topic_title"), margin, height * 0.3); ctx.fillText(i18n("wizard.previews.topic_title"), margin, height * 0.3);
const bodyFontSize = height / 330.0; // Topic OP text
const bodyFontSize = 1;
ctx.font = `${bodyFontSize}em '${font}'`; ctx.font = `${bodyFontSize}em '${font}'`;
let line = 0; let verticalLinePos = 0;
const lines = i18n("wizard.homepage_preview.topic_ops.what_books").split( const topicOp = i18n("wizard.homepage_preview.topic_ops.what_books");
"\n" const topicOpLines = topicOp.split("\n");
this.resizeTextLinesToFitRect(
topicOpLines,
timelineX - leftHandTextGutter,
ctx,
bodyFontSize,
font,
(textLine, idx) => {
verticalLinePos = height * 0.4 + idx * lineHeight;
ctx.fillText(textLine, leftHandTextGutter, verticalLinePos);
}
); );
for (let i = 0; i < lines.length; i++) {
line = height * 0.4 + i * lineHeight;
ctx.fillText(lines[i], margin + avatarSize + margin, line);
}
ctx.font = `${bodyFontSize}em '${font}'`; ctx.font = `${bodyFontSize}em '${font}'`;
// Share Button // Share button
const shareButtonWidth = const shareButtonWidth =
Math.round(ctx.measureText(i18n("wizard.previews.share_button")).width) + Math.round(ctx.measureText(i18n("wizard.previews.share_button")).width) +
20; margin;
ctx.beginPath(); ctx.beginPath();
ctx.rect(margin, line + lineHeight, shareButtonWidth, height * 0.1); ctx.rect(margin, verticalLinePos, shareButtonWidth, height * 0.1);
// accounts for hard-set color variables in solarized themes // accounts for hard-set color variables in solarized themes
ctx.fillStyle = ctx.fillStyle =
colors.primary_low || colors.primary_low ||
@ -165,18 +174,18 @@ export default class Index extends PreviewBaseComponent {
ctx.fillText( ctx.fillText(
i18n("wizard.previews.share_button"), i18n("wizard.previews.share_button"),
margin + 10, margin + 10,
line + lineHeight * 1.9 verticalLinePos + lineHeight * 0.9
); );
// Reply Button // Reply button
const replyButtonWidth = const replyButtonWidth =
Math.round(ctx.measureText(i18n("wizard.previews.reply_button")).width) + Math.round(ctx.measureText(i18n("wizard.previews.reply_button")).width) +
20; margin;
ctx.beginPath(); ctx.beginPath();
ctx.rect( ctx.rect(
shareButtonWidth + margin + 10, shareButtonWidth + margin + 10,
line + lineHeight, verticalLinePos,
replyButtonWidth, replyButtonWidth,
height * 0.1 height * 0.1
); );
@ -185,12 +194,11 @@ export default class Index extends PreviewBaseComponent {
ctx.fillStyle = colors.secondary; ctx.fillStyle = colors.secondary;
ctx.fillText( ctx.fillText(
i18n("wizard.previews.reply_button"), i18n("wizard.previews.reply_button"),
shareButtonWidth + margin + 20, shareButtonWidth + margin * 2,
line + lineHeight * 1.9 verticalLinePos + lineHeight * 0.9
); );
// Draw Timeline // Draw timeline
const timelineX = width * 0.86;
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = colors.tertiary; ctx.strokeStyle = colors.tertiary;
ctx.lineWidth = 0.5; ctx.lineWidth = 0.5;
@ -198,17 +206,29 @@ export default class Index extends PreviewBaseComponent {
ctx.lineTo(timelineX, height * 0.7); ctx.lineTo(timelineX, height * 0.7);
ctx.stroke(); ctx.stroke();
// Timeline // Timeline handle
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = colors.tertiary; ctx.strokeStyle = colors.tertiary;
ctx.lineWidth = 2; ctx.lineWidth = 3;
ctx.moveTo(timelineX, height * 0.3); ctx.moveTo(timelineX, height * 0.3 + 10);
ctx.lineTo(timelineX, height * 0.4); ctx.lineTo(timelineX, height * 0.4);
ctx.stroke(); ctx.stroke();
// Timeline post count
const postCountY = height * 0.3 + margin + 10;
ctx.font = `Bold ${bodyFontSize}em ${font}`; ctx.font = `Bold ${bodyFontSize}em ${font}`;
ctx.fillStyle = colors.primary; ctx.fillStyle = colors.primary;
ctx.fillText("1 / 20", timelineX + margin, height * 0.3 + margin * 1.5); ctx.fillText("1 / 20", timelineX + margin / 2, postCountY);
// Timeline post date
ctx.beginPath();
ctx.font = `${bodyFontSize * 0.9}em ${font}`;
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 70, 65);
ctx.fillText(
"Nov 22",
timelineX + margin / 2,
postCountY + lineHeight * 0.75
);
} }
@action @action

View File

@ -151,6 +151,12 @@ body.wizard {
} }
} }
&__step.styling .wizard-container__field.styling-preview-field {
label {
display: none;
}
}
&__field { &__field {
margin-bottom: 1em; margin-bottom: 1em;
} }