UX: Lots of wizard canvas rendering improvements
* Make sure bold body font loads before rendering otherwise the 1/20 post count on the timeline will use wrong font * Fix pill button rendering on homepage preview, now all the widths resize based on the font size/width and all buttons depend on the X position of the button beforehand * Fix the table header labels overlapping by giving them more room based on col position * Moving hardcoded label text into I18n
This commit is contained in:
parent
9c6e5440d8
commit
7c92402e15
|
@ -83,7 +83,7 @@ export default class HomepagePreview extends PreviewBaseComponent {
|
|||
]
|
||||
);
|
||||
|
||||
ctx.font = `Bold ${bodyFontSize * 1.3}em '${font}'`;
|
||||
ctx.font = `700 ${bodyFontSize * 1.3}em '${font}'`;
|
||||
ctx.fillStyle = colors.primary;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(category.name, boxStartX + boxWidth / 2, boxStartY + 25);
|
||||
|
@ -166,7 +166,7 @@ export default class HomepagePreview extends PreviewBaseComponent {
|
|||
// Categories
|
||||
this.categories().forEach((category, idx) => {
|
||||
const textPos = y + categoryHeight * 0.35;
|
||||
ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`;
|
||||
ctx.font = `700 ${bodyFontSize * 1.1}em '${font}'`;
|
||||
ctx.fillStyle = colors.primary;
|
||||
ctx.fillText(category.name, cols[0], textPos);
|
||||
|
||||
|
@ -262,7 +262,7 @@ export default class HomepagePreview extends PreviewBaseComponent {
|
|||
// Categories
|
||||
this.categories().forEach((category, idx) => {
|
||||
const textPos = y + categoryHeight * 0.35;
|
||||
ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`;
|
||||
ctx.font = `700 ${bodyFontSize * 1.1}em '${font}'`;
|
||||
ctx.fillStyle = colors.primary;
|
||||
ctx.fillText(category.name, cols[0], textPos);
|
||||
|
||||
|
@ -310,7 +310,7 @@ export default class HomepagePreview extends PreviewBaseComponent {
|
|||
);
|
||||
ctx.fillText(title, cols[3], textPos);
|
||||
|
||||
ctx.font = `Bold ${bodyFontSize}em '${font}'`;
|
||||
ctx.font = `700 ${bodyFontSize}em '${font}'`;
|
||||
ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[4], textPos);
|
||||
ctx.font = `${bodyFontSize}em '${font}'`;
|
||||
ctx.fillText(`1h`, cols[4], textPos + topicHeight * 0.4);
|
||||
|
@ -318,7 +318,7 @@ export default class HomepagePreview extends PreviewBaseComponent {
|
|||
ctx.beginPath();
|
||||
ctx.fillStyle = category.color;
|
||||
const badgeSize = topicHeight * 0.1;
|
||||
ctx.font = `Bold ${bodyFontSize * 0.5}em '${font}'`;
|
||||
ctx.font = `700 ${bodyFontSize * 0.5}em '${font}'`;
|
||||
ctx.rect(
|
||||
cols[3] + margin * 0.25,
|
||||
y + topicHeight * 0.65,
|
||||
|
@ -383,17 +383,33 @@ export default class HomepagePreview extends PreviewBaseComponent {
|
|||
ctx.stroke();
|
||||
};
|
||||
|
||||
const cols = [0.02, 0.66, 0.8, 0.87, 0.93].map((c) => c * width);
|
||||
const cols = [0.02, 0.66, 0.75, 0.83, 0.9].map((c) => c * width);
|
||||
|
||||
// Headings
|
||||
const headingY = height * 0.33;
|
||||
|
||||
ctx.fillStyle = textColor;
|
||||
ctx.font = `${bodyFontSize * 0.9}em '${font}'`;
|
||||
ctx.fillText("Topic", cols[0], headingY);
|
||||
ctx.fillText("Replies", cols[2], headingY);
|
||||
ctx.fillText("Views", cols[3], headingY);
|
||||
ctx.fillText("Activity", cols[4], headingY);
|
||||
ctx.fillText(
|
||||
i18n("wizard.homepage_preview.table_headers.topic"),
|
||||
cols[0],
|
||||
headingY
|
||||
);
|
||||
ctx.fillText(
|
||||
i18n("wizard.homepage_preview.table_headers.replies"),
|
||||
cols[2],
|
||||
headingY
|
||||
);
|
||||
ctx.fillText(
|
||||
i18n("wizard.homepage_preview.table_headers.views"),
|
||||
cols[3],
|
||||
headingY
|
||||
);
|
||||
ctx.fillText(
|
||||
i18n("wizard.homepage_preview.table_headers.activity"),
|
||||
cols[4],
|
||||
headingY
|
||||
);
|
||||
|
||||
// Topics
|
||||
let y = headingY + rowHeight / 2.6;
|
||||
|
@ -412,7 +428,7 @@ export default class HomepagePreview extends PreviewBaseComponent {
|
|||
ctx.beginPath();
|
||||
ctx.fillStyle = category.color;
|
||||
const badgeSize = rowHeight * 0.15;
|
||||
ctx.font = `Bold ${bodyFontSize * 0.75}em '${font}'`;
|
||||
ctx.font = `700 ${bodyFontSize * 0.75}em '${font}'`;
|
||||
ctx.rect(cols[0] + 4, y + rowHeight * 0.6, badgeSize, badgeSize);
|
||||
ctx.fill();
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ export default class PreviewBase extends Component {
|
|||
});
|
||||
});
|
||||
|
||||
Promise.all(
|
||||
return Promise.all(
|
||||
fontFaces.map((fontFace) =>
|
||||
fontFace.load().then((loadedFont) => {
|
||||
document.fonts.add(loadedFont);
|
||||
|
@ -135,7 +135,7 @@ export default class PreviewBase extends Component {
|
|||
this.loadingFontVariants = false;
|
||||
});
|
||||
} else if (this.loadedFonts.has(font.id)) {
|
||||
this.triggerRepaint();
|
||||
return Promise.resolve(this.triggerRepaint());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,8 +162,14 @@ export default class PreviewBase extends Component {
|
|||
|
||||
reload() {
|
||||
Promise.all([this.loadFonts(), this.loadImages()]).then(() => {
|
||||
this.loaded = true;
|
||||
this.triggerRepaint();
|
||||
// NOTE: This must be done otherwise the "bold" variant of the body font
|
||||
// will not be loaded for some reason before rendering the canvas.
|
||||
//
|
||||
// The header font does not suffer from this issue.
|
||||
this.loadFontVariants(this.wizard.font).then(() => {
|
||||
this.loaded = true;
|
||||
this.triggerRepaint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -283,17 +289,16 @@ export default class PreviewBase extends Component {
|
|||
avatarSize,
|
||||
avatarSize
|
||||
);
|
||||
|
||||
// accounts for hard-set color variables in solarized themes
|
||||
ctx.fillStyle =
|
||||
colors.primary_low_mid ||
|
||||
darkLightDiff(colors.primary, colors.secondary, 45, 55);
|
||||
|
||||
const pathScale = this.headerHeight / 1200;
|
||||
// search icon SVG path
|
||||
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"
|
||||
);
|
||||
// hamburger icon
|
||||
const hamburgerIcon = new Path2D(
|
||||
"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"
|
||||
);
|
||||
|
@ -321,100 +326,100 @@ export default class PreviewBase extends Component {
|
|||
|
||||
const { ctx } = this;
|
||||
|
||||
const categoriesSize = headerHeight * 2;
|
||||
const badgeHeight = categoriesSize * 0.25;
|
||||
const badgeHeight = headerHeight * 2 * 0.25;
|
||||
const headerMargin = headerHeight * 0.2;
|
||||
const fontSize = Math.round(badgeHeight * 0.5);
|
||||
ctx.font = `${fontSize}px '${font}'`;
|
||||
|
||||
const allCategoriesText = i18n(
|
||||
"wizard.homepage_preview.nav_buttons.all_categories"
|
||||
);
|
||||
const categoriesWidth = ctx.measureText(allCategoriesText).width;
|
||||
const categoriesBoxWidth = categoriesWidth + headerMargin * 2;
|
||||
|
||||
// Box around "all categories >"
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = colors.primary;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.rect(
|
||||
headerMargin,
|
||||
headerHeight + headerMargin,
|
||||
categoriesSize,
|
||||
categoriesBoxWidth,
|
||||
badgeHeight
|
||||
);
|
||||
ctx.stroke();
|
||||
|
||||
const fontSize = Math.round(badgeHeight * 0.5);
|
||||
|
||||
ctx.font = `${fontSize}px '${font}'`;
|
||||
ctx.fillStyle = colors.primary;
|
||||
ctx.fillText(
|
||||
"all categories",
|
||||
allCategoriesText,
|
||||
headerMargin * 1.5,
|
||||
headerHeight + headerMargin * 1.4 + fontSize
|
||||
);
|
||||
|
||||
// Caret (>) at the end of "all categories" box
|
||||
const pathScale = badgeHeight / 1000;
|
||||
// caret icon
|
||||
const caretIcon = new Path2D(
|
||||
"M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
|
||||
);
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(
|
||||
categoriesSize - headerMargin / 4,
|
||||
categoriesBoxWidth,
|
||||
headerHeight + headerMargin + badgeHeight / 4
|
||||
);
|
||||
ctx.scale(pathScale, pathScale);
|
||||
ctx.fill(caretIcon);
|
||||
ctx.restore();
|
||||
|
||||
const text = opts.categories ? "Categories" : "Latest";
|
||||
// First top menu item
|
||||
const firstTopMenuItemText = opts.categories
|
||||
? i18n("wizard.homepage_preview.nav_buttons.categories")
|
||||
: i18n("wizard.homepage_preview.nav_buttons.latest");
|
||||
const newText = i18n("wizard.homepage_preview.nav_buttons.new");
|
||||
const unreadText = i18n("wizard.homepage_preview.nav_buttons.unread");
|
||||
const topText = i18n("wizard.homepage_preview.nav_buttons.top");
|
||||
const hotText = i18n("wizard.homepage_preview.nav_buttons.hot");
|
||||
|
||||
const activeWidth = categoriesSize * (opts.categories ? 0.8 : 0.55);
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = colors.tertiary;
|
||||
ctx.rect(
|
||||
headerMargin * 2 + categoriesSize,
|
||||
categoriesBoxWidth + headerMargin * 2,
|
||||
headerHeight + headerMargin,
|
||||
activeWidth,
|
||||
ctx.measureText(firstTopMenuItemText).width + headerMargin * 2,
|
||||
badgeHeight
|
||||
);
|
||||
ctx.fill();
|
||||
|
||||
ctx.font = `${fontSize}px '${font}'`;
|
||||
ctx.fillStyle = colors.secondary;
|
||||
let x = headerMargin * 3.0 + categoriesSize;
|
||||
const pillButtonTextY = headerHeight + headerMargin * 1.4 + fontSize;
|
||||
const firstTopMenuItemX = headerMargin * 3.0 + categoriesBoxWidth;
|
||||
ctx.fillText(
|
||||
text,
|
||||
x - headerMargin * 0.1,
|
||||
headerHeight + headerMargin * 1.5 + fontSize
|
||||
firstTopMenuItemText,
|
||||
firstTopMenuItemX,
|
||||
pillButtonTextY,
|
||||
ctx.measureText(firstTopMenuItemText).width
|
||||
);
|
||||
|
||||
ctx.fillStyle = colors.primary;
|
||||
x += categoriesSize * (opts.categories ? 0.8 : 0.6);
|
||||
ctx.fillText("New", x, headerHeight + headerMargin * 1.5 + fontSize);
|
||||
|
||||
x += categoriesSize * 0.4;
|
||||
ctx.fillText("Unread", x, headerHeight + headerMargin * 1.5 + fontSize);
|
||||
const newTextX =
|
||||
firstTopMenuItemX +
|
||||
ctx.measureText(firstTopMenuItemText).width +
|
||||
headerMargin * 2.0;
|
||||
ctx.fillText(newText, newTextX, pillButtonTextY);
|
||||
|
||||
x += categoriesSize * 0.6;
|
||||
ctx.fillText("Top", x, headerHeight + headerMargin * 1.5 + fontSize);
|
||||
}
|
||||
const unreadTextX =
|
||||
newTextX + ctx.measureText(newText).width + headerMargin * 2.0;
|
||||
ctx.fillText(unreadText, unreadTextX, pillButtonTextY);
|
||||
|
||||
resizeTextLinesToFitRect(
|
||||
textLines,
|
||||
rectWidth,
|
||||
ctx,
|
||||
fontSize,
|
||||
font,
|
||||
renderCallback
|
||||
) {
|
||||
const maxLengthLine = textLines.reduce((a, b) =>
|
||||
a.length > b.length ? a : b
|
||||
);
|
||||
const topTextX =
|
||||
unreadTextX + ctx.measureText(unreadText).width + headerMargin * 2.0;
|
||||
ctx.fillText(topText, topTextX, pillButtonTextY);
|
||||
|
||||
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);
|
||||
}
|
||||
const hotTextX =
|
||||
topTextX + ctx.measureText(topText).width + headerMargin * 2.0;
|
||||
ctx.fillText(hotText, hotTextX, pillButtonTextY);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@ import { action } from "@ember/object";
|
|||
import { observes } from "@ember-decorators/object";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import { chooseDarker, darkLightDiff } from "../../../lib/preview";
|
||||
import {
|
||||
chooseDarker,
|
||||
darkLightDiff,
|
||||
resizeTextLinesToFitRect,
|
||||
} from "../../../lib/preview";
|
||||
import HomepagePreview from "./-homepage-preview";
|
||||
import PreviewBaseComponent from "./-preview-base";
|
||||
|
||||
|
@ -133,7 +137,7 @@ export default class Index extends PreviewBaseComponent {
|
|||
// Topic title
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = colors.primary;
|
||||
ctx.font = `bold ${titleFontSize}em '${headingFont}'`;
|
||||
ctx.font = `700 ${titleFontSize}em '${headingFont}'`;
|
||||
ctx.fillText(i18n("wizard.previews.topic_title"), margin, height * 0.3);
|
||||
|
||||
// Topic OP text
|
||||
|
@ -144,7 +148,7 @@ export default class Index extends PreviewBaseComponent {
|
|||
const topicOp = i18n("wizard.homepage_preview.topic_ops.what_books");
|
||||
const topicOpLines = topicOp.split("\n");
|
||||
|
||||
this.resizeTextLinesToFitRect(
|
||||
resizeTextLinesToFitRect(
|
||||
topicOpLines,
|
||||
timelineX - leftHandTextGutter,
|
||||
ctx,
|
||||
|
@ -216,13 +220,14 @@ export default class Index extends PreviewBaseComponent {
|
|||
|
||||
// Timeline post count
|
||||
const postCountY = height * 0.3 + margin + 10;
|
||||
ctx.font = `Bold ${bodyFontSize}em ${font}`;
|
||||
ctx.beginPath();
|
||||
ctx.font = `700 ${bodyFontSize}em '${font}'`;
|
||||
ctx.fillStyle = colors.primary;
|
||||
ctx.fillText("1 / 20", timelineX + margin / 2, postCountY);
|
||||
|
||||
// Timeline post date
|
||||
ctx.beginPath();
|
||||
ctx.font = `${bodyFontSize * 0.9}em ${font}`;
|
||||
ctx.font = `${bodyFontSize * 0.9}em '${font}'`;
|
||||
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 70, 65);
|
||||
ctx.fillText(
|
||||
"Nov 22",
|
||||
|
|
|
@ -138,3 +138,26 @@ export function drawHeader(ctx, colors, width, headerHeight) {
|
|||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
export function 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ class SiteSetting < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.top_menu_items
|
||||
top_menu.split("|").map { |menu_item| TopMenuItem.new(menu_item) }
|
||||
top_menu_map.map { |menu_item| TopMenuItem.new(menu_item) }
|
||||
end
|
||||
|
||||
def self.homepage
|
||||
|
|
|
@ -7390,6 +7390,14 @@ en:
|
|||
homepage_preview: "Homepage preview"
|
||||
|
||||
homepage_preview:
|
||||
nav_buttons:
|
||||
all_categories: "all categories"
|
||||
new: "New"
|
||||
unread: "Unread"
|
||||
top: "Top"
|
||||
latest: "Latest"
|
||||
hot: "Hot"
|
||||
categories: "Categories"
|
||||
topic_titles:
|
||||
what_books: "What books are you reading?"
|
||||
what_movies: "What movies have you seen recently?"
|
||||
|
@ -7409,3 +7417,8 @@ en:
|
|||
icebreakers: "Icebreakers"
|
||||
news: "News"
|
||||
site_feedback: "Site Feedback"
|
||||
table_headers:
|
||||
topic: "Topic"
|
||||
replies: "Replies"
|
||||
views: "Views"
|
||||
activity: "Activity"
|
||||
|
|
|
@ -5423,6 +5423,8 @@ en:
|
|||
choices:
|
||||
latest:
|
||||
label: "Latest Topics"
|
||||
hot:
|
||||
label: "Hot Topics"
|
||||
categories_only:
|
||||
label: "Categories Only"
|
||||
categories_with_featured_topics:
|
||||
|
|
|
@ -222,7 +222,7 @@ module Stylesheet
|
|||
)
|
||||
contents << <<~CSS
|
||||
@font-face {
|
||||
font-family: #{font[:name]};
|
||||
font-family: '#{font[:name]}';
|
||||
src: #{src};
|
||||
font-weight: #{variant[:weight]};
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ class Wizard
|
|||
|
||||
current =
|
||||
(
|
||||
if SiteSetting.top_menu.starts_with?("categories")
|
||||
if SiteSetting.top_menu_map.first == "categories"
|
||||
SiteSetting.desktop_category_page_style
|
||||
else
|
||||
"latest"
|
||||
|
@ -214,8 +214,8 @@ class Wizard
|
|||
updater.update_setting(:base_font, updater.fields[:body_font])
|
||||
updater.update_setting(:heading_font, updater.fields[:heading_font])
|
||||
|
||||
top_menu = SiteSetting.top_menu.split("|")
|
||||
if updater.fields[:homepage_style] == "latest" && top_menu[0] != "latest"
|
||||
top_menu = SiteSetting.top_menu_map
|
||||
if updater.fields[:homepage_style] == "latest" && top_menu.first != "latest"
|
||||
top_menu.delete("latest")
|
||||
top_menu.insert(0, "latest")
|
||||
elsif updater.fields[:homepage_style] != "latest"
|
||||
|
|
Loading…
Reference in New Issue