discourse/app/assets/javascripts/wizard/addon/components/homepage-preview.js
Godfrey Chan 2228f75645
DEV: Modernize Wizard model implementation (#23640)
+ native classes
+ tracked properties
- Ember.Object
- Ember.Evented
- observers
- mixins
- computed/discourseComputed

Also removes unused wizard infrastructure for warnings. It appears
that once upon on time, either the server can generate warnings,
or some client code can generate them, which requires an extra 
confirmation from the user before they can continue to the next step.

This code is not tested and appears unused and defunct. Nothing
generates such warning and the server does not serialize them.

Extracted from https://github.com/discourse/discourse/pull/23678
2023-11-23 16:35:51 +00:00

486 lines
14 KiB
JavaScript

import { darkLightDiff, LOREM } 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.logoUrl,
avatar: "/images/wizard/trout.png",
};
},
paint({ ctx, colors, font, width, height }) {
this.drawFullHeader(colors, font, this.logo);
const homepageStyle = this.getHomepageStyle();
if (homepageStyle === "latest") {
this.drawPills(colors, font, height * 0.15);
this.renderLatest(ctx, colors, font, width, height);
} else if (
["categories_only", "categories_with_featured_topics"].includes(
homepageStyle
)
) {
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(
homepageStyle
)
) {
this.drawPills(colors, font, height * 0.15, { categories: true });
const topics = homepageStyle === "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);
const homepageStyle = this.getHomepageStyle();
if (homepageStyle === "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 (homepageStyle === "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 (homepageStyle === "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.getHomepageStyle() === "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);
});
},
getHomepageStyle() {
return this.step.valueFor("homepage_style");
},
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();
});
},
});