discourse/app/assets/javascripts/wizard/addon/components/homepage-preview.js
David Taylor 1506017767
DEV: Refactor wizard preview components to use inheritence (#20282)
The previous `createPreviewComponent` implementation was problematic for template colocation. We can achieve the same result using normal component class inheritance.
2023-02-14 14:20:15 +00:00

490 lines
14 KiB
JavaScript

import { LOREM, darkLightDiff } from "wizard/lib/preview";
import WizardPreviewBaseComponent from "./wizard-preview-base";
export default WizardPreviewBaseComponent.extend({
width: 628,
height: 322,
logo: null,
avatar: null,
didUpdateAttrs() {
this._super(...arguments);
this.triggerRepaint();
},
images() {
return {
logo: this.wizard.getLogoUrl(),
avatar: "/images/wizard/trout.png",
};
},
paint({ ctx, colors, font, width, height }) {
this.drawFullHeader(colors, font, this.logo);
if (this.get("step.fieldsById.homepage_style.value") === "latest") {
this.drawPills(colors, font, height * 0.15);
this.renderLatest(ctx, colors, font, width, height);
} else if (
["categories_only", "categories_with_featured_topics"].includes(
this.get("step.fieldsById.homepage_style.value")
)
) {
this.drawPills(colors, font, height * 0.15, { categories: true });
this.renderCategories(ctx, colors, font, width, height);
} else if (
["categories_boxes", "categories_boxes_with_topics"].includes(
this.get("step.fieldsById.homepage_style.value")
)
) {
this.drawPills(colors, font, height * 0.15, { categories: true });
const topics =
this.get("step.fieldsById.homepage_style.value") ===
"categories_boxes_with_topics";
this.renderCategoriesBoxes(ctx, colors, font, width, height, { topics });
} else {
this.drawPills(colors, font, height * 0.15, { categories: true });
this.renderCategoriesWithTopics(ctx, colors, font, width, height);
}
},
renderCategoriesBoxes(ctx, colors, font, width, height, opts) {
opts = opts || {};
const borderColor = darkLightDiff(
colors.primary,
colors.secondary,
90,
-75
);
const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50);
const margin = height * 0.03;
const bodyFontSize = height / 440.0;
const boxHeight = height * 0.7 - margin * 2;
const descriptions = this.getDescriptions();
const boxesSpacing = 15;
const boxWidth = (width - margin * 2 - boxesSpacing * 2) / 3;
this.categories().forEach((category, index) => {
const boxStartX = margin + index * boxWidth + index * boxesSpacing;
const boxStartY = height * 0.33;
this.drawSquare(
ctx,
{ x: boxStartX, y: boxStartY },
{ x: boxStartX + boxWidth, y: boxStartY + boxHeight },
[
{ color: borderColor },
{ color: borderColor },
{ color: borderColor },
{ color: category.color, width: 5 },
]
);
ctx.font = `Bold ${bodyFontSize * 1.3}em '${font}'`;
ctx.fillStyle = colors.primary;
ctx.textAlign = "center";
ctx.fillText(category.name, boxStartX + boxWidth / 2, boxStartY + 25);
ctx.textAlign = "left";
if (opts.topics) {
let startY = boxStartY + 60;
this.getTitles().forEach((title) => {
ctx.font = `${bodyFontSize * 1}em '${font}'`;
ctx.fillStyle = colors.tertiary;
startY +=
this.fillTextMultiLine(
ctx,
title.split("\n").join(" "),
boxStartX + 10,
startY,
13,
boxWidth * 0.95
) + 8;
});
} else {
ctx.font = `${bodyFontSize * 1}em '${font}'`;
ctx.fillStyle = textColor;
ctx.textAlign = "center";
this.fillTextMultiLine(
ctx,
descriptions[index],
boxStartX + boxWidth / 2,
boxStartY + 60,
13,
boxWidth * 0.8
);
ctx.textAlign = "left";
}
});
},
renderCategories(ctx, colors, font, width, height) {
const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50);
const margin = height * 0.03;
const bodyFontSize = height / 440.0;
const titles = this.getTitles();
let categoryHeight = height / 6;
const drawLine = (x, y) => {
ctx.beginPath();
ctx.strokeStyle = darkLightDiff(
colors.primary,
colors.secondary,
90,
-75
);
ctx.moveTo(margin + x, y);
ctx.lineTo(width - margin, y);
ctx.stroke();
};
const cols = [0.025, 0.45, 0.53, 0.58, 0.94, 0.96].map((c) => c * width);
const headingY = height * 0.33;
ctx.font = `${bodyFontSize * 0.9}em '${font}'`;
ctx.fillStyle = textColor;
ctx.fillText("Category", cols[0], headingY);
if (
this.get("step.fieldsById.homepage_style.value") === "categories_only"
) {
ctx.fillText("Topics", cols[4], headingY);
} else {
ctx.fillText("Topics", cols[1], headingY);
ctx.fillText("Latest", cols[2], headingY);
categoryHeight = height / 5;
}
let y = headingY + bodyFontSize * 12;
ctx.lineWidth = 2;
drawLine(0, y);
drawLine(width / 2, y);
// Categories
this.categories().forEach((category) => {
const textPos = y + categoryHeight * 0.35;
ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`;
ctx.fillStyle = colors.primary;
ctx.fillText(category.name, cols[0], textPos);
ctx.font = `${bodyFontSize * 0.8}em '${font}'`;
ctx.fillStyle = textColor;
ctx.fillText(
titles[0],
cols[0] - margin * 0.25,
textPos + categoryHeight * 0.36
);
ctx.beginPath();
ctx.moveTo(margin, y);
ctx.strokeStyle = category.color;
ctx.lineWidth = 3.5;
ctx.lineTo(margin, y + categoryHeight);
ctx.stroke();
if (
this.get("step.fieldsById.homepage_style.value") ===
"categories_with_featured_topics"
) {
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(
Math.floor(Math.random() * 90) + 10,
cols[1] + 15,
textPos
);
} else {
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[5], textPos);
}
y += categoryHeight;
ctx.lineWidth = 1;
drawLine(0, y);
});
// Featured Topics
if (
this.get("step.fieldsById.homepage_style.value") ===
"categories_with_featured_topics"
) {
const topicHeight = height / 15;
y = headingY + bodyFontSize * 22;
ctx.lineWidth = 1;
ctx.fillStyle = colors.tertiary;
titles.forEach((title) => {
ctx.font = `${bodyFontSize}em '${font}'`;
const textPos = y + topicHeight * 0.35;
ctx.fillStyle = colors.tertiary;
ctx.fillText(`${title}`, cols[2], textPos);
y += topicHeight;
});
}
},
renderCategoriesWithTopics(ctx, colors, font, width, height) {
const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50);
const margin = height * 0.03;
const bodyFontSize = height / 440.0;
const drawLine = (x, y) => {
ctx.beginPath();
ctx.strokeStyle = darkLightDiff(
colors.primary,
colors.secondary,
90,
-75
);
ctx.moveTo(margin + x, y);
ctx.lineTo(margin + x + (width * 0.9) / 2, y);
ctx.stroke();
};
const cols = [0.025, 0.42, 0.53, 0.58, 0.94].map((c) => c * width);
const headingY = height * 0.33;
ctx.font = `${bodyFontSize * 0.9}em '${font}'`;
ctx.fillStyle = textColor;
ctx.fillText("Category", cols[0], headingY);
ctx.fillText("Topics", cols[1], headingY);
if (
this.get("step.fieldsById.homepage_style.value") ===
"categories_and_latest_topics"
) {
ctx.fillText("Latest", cols[2], headingY);
} else {
ctx.fillText("Top", cols[2], headingY);
}
let y = headingY + bodyFontSize * 12;
ctx.lineWidth = 2;
drawLine(0, y);
drawLine(width / 2, y);
const categoryHeight = height / 6;
const titles = this.getTitles();
// Categories
this.categories().forEach((category) => {
const textPos = y + categoryHeight * 0.35;
ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`;
ctx.fillStyle = colors.primary;
ctx.fillText(category.name, cols[0], textPos);
ctx.font = `${bodyFontSize * 0.8}em '${font}'`;
ctx.fillStyle = textColor;
ctx.fillText(
titles[0],
cols[0] - margin * 0.25,
textPos + categoryHeight * 0.36
);
ctx.beginPath();
ctx.moveTo(margin, y);
ctx.strokeStyle = category.color;
ctx.lineWidth = 3.5;
ctx.lineTo(margin, y + categoryHeight);
ctx.stroke();
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[1] + 15, textPos);
y += categoryHeight;
ctx.lineWidth = 1;
drawLine(0, y);
});
// Latest/Top Topics
const topicHeight = height / 8;
const avatarSize = topicHeight * 0.7;
y = headingY + bodyFontSize * 12;
ctx.lineWidth = 1;
ctx.fillStyle = textColor;
titles.forEach((title) => {
const category = this.categories()[0];
ctx.font = `${bodyFontSize}em '${font}'`;
const textPos = y + topicHeight * 0.45;
ctx.fillStyle = textColor;
this.scaleImage(
this.avatar,
cols[2],
y + margin * 0.6,
avatarSize,
avatarSize
);
ctx.fillText(title, cols[3], textPos);
ctx.font = `Bold ${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);
ctx.beginPath();
ctx.fillStyle = category.color;
const badgeSize = topicHeight * 0.1;
ctx.font = `Bold ${bodyFontSize * 0.5}em '${font}'`;
ctx.rect(
cols[3] + margin * 0.5,
y + topicHeight * 0.65,
badgeSize,
badgeSize
);
ctx.fill();
ctx.fillStyle = colors.primary;
ctx.fillText(
category.name,
cols[3] + badgeSize * 3,
y + topicHeight * 0.76
);
y += topicHeight;
drawLine(width / 2, y);
});
},
getTitles() {
return LOREM.split(".")
.slice(0, 8)
.map((t) => t.substring(0, 40));
},
getDescriptions() {
return LOREM.split(".");
},
renderLatest(ctx, colors, font, width, height) {
const rowHeight = height / 6.6;
// accounts for hard-set color variables in solarized themes
const textColor =
colors.primary_medium ||
darkLightDiff(colors.primary, colors.secondary, 50, 50);
const bodyFontSize = height / 440.0;
ctx.font = `${bodyFontSize}em '${font}'`;
const margin = height * 0.03;
const drawLine = (y) => {
ctx.beginPath();
// accounts for hard-set color variables in solarized themes
ctx.strokeStyle =
colors.primary_low ||
darkLightDiff(colors.primary, colors.secondary, 90, -75);
ctx.moveTo(margin, y);
ctx.lineTo(width - margin, y);
ctx.stroke();
};
const cols = [0.02, 0.66, 0.8, 0.87, 0.93].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);
// Topics
let y = headingY + rowHeight / 2.6;
ctx.lineWidth = 2;
drawLine(y);
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.lineWidth = 1;
this.getTitles().forEach((title) => {
const textPos = y + rowHeight * 0.4;
ctx.fillStyle = textColor;
ctx.fillText(title, cols[0], textPos);
const category = this.categories()[0];
ctx.beginPath();
ctx.fillStyle = category.color;
const badgeSize = rowHeight * 0.15;
ctx.font = `Bold ${bodyFontSize * 0.75}em '${font}'`;
ctx.rect(cols[0] + 4, y + rowHeight * 0.6, badgeSize, badgeSize);
ctx.fill();
ctx.fillStyle = colors.primary;
ctx.fillText(
category.name,
cols[0] + badgeSize * 2,
y + rowHeight * 0.73
);
this.scaleImage(
this.avatar,
cols[1],
y + rowHeight * 0.25,
rowHeight * 0.5,
rowHeight * 0.5
);
ctx.fillStyle = textColor;
ctx.font = `${bodyFontSize}em '${font}'`;
for (let j = 2; j <= 4; j++) {
ctx.fillText(
j === 4 ? "1h" : Math.floor(Math.random() * 90) + 10,
cols[j] + margin,
y + rowHeight * 0.6
);
}
drawLine(y + rowHeight * 1);
y += rowHeight;
});
},
fillTextMultiLine(ctx, text, x, y, lineHeight, maxWidth) {
const words = text.split(" ").filter((f) => f);
let line = "";
let totalHeight = 0;
words.forEach((word) => {
if (ctx.measureText(`${line} ${word} `).width >= maxWidth) {
ctx.fillText(line, x, y + totalHeight);
totalHeight += lineHeight;
line = word.trim();
} else {
line = `${line} ${word}`.trim();
}
});
ctx.fillText(line, x, y + totalHeight);
totalHeight += lineHeight;
return totalHeight;
},
// Edges expected in this order: NW to NE -> NE to SE -> SE to SW -> SW to NW
drawSquare(ctx, from, to, edges = []) {
const edgeConfiguration = (index) => {
const edge = edges[index] || {};
return {
width: edge.width || 1,
color: edge.color || "#333",
};
};
[
{ from: { x: from.x, y: from.y }, to: { x: to.x, y: from.y } },
{ from: { x: to.x, y: from.y }, to: { x: to.x, y: to.y } },
{ from: { x: to.x, y: to.y }, to: { x: from.x, y: to.y } },
{ from: { x: from.x, y: to.y }, to: { x: from.x, y: from.y } },
].forEach((path, index) => {
const configuration = edgeConfiguration(index);
ctx.beginPath();
ctx.moveTo(path.from.x, path.from.y);
ctx.strokeStyle = configuration.color;
ctx.lineWidth = configuration.width;
ctx.lineTo(path.to.x, path.to.y);
ctx.stroke();
});
},
});