mirror of
https://github.com/discourse/discourse.git
synced 2024-12-11 23:13:48 +08:00
a98182debf
Followup to f8f281cdad
, thanks @jjafeux
490 lines
14 KiB
JavaScript
490 lines
14 KiB
JavaScript
import { observes } from "discourse-common/utils/decorators";
|
|
import {
|
|
createPreviewComponent,
|
|
LOREM,
|
|
darkLightDiff
|
|
} from "wizard/lib/preview";
|
|
|
|
export default createPreviewComponent(659, 320, {
|
|
logo: null,
|
|
avatar: null,
|
|
|
|
@observes("step.fieldsById.homepage_style.value")
|
|
styleChanged() {
|
|
this.triggerRepaint();
|
|
},
|
|
|
|
images() {
|
|
return {
|
|
logo: this.wizard.getLogoUrl(),
|
|
avatar: "/images/wizard/trout.png"
|
|
};
|
|
},
|
|
|
|
paint(ctx, colors, width, height) {
|
|
this.drawFullHeader(colors);
|
|
|
|
if (this.get("step.fieldsById.homepage_style.value") === "latest") {
|
|
this.drawPills(colors, height * 0.15);
|
|
this.renderLatest(ctx, colors, width, height);
|
|
} else if (
|
|
["categories_only", "categories_with_featured_topics"].includes(
|
|
this.get("step.fieldsById.homepage_style.value")
|
|
)
|
|
) {
|
|
this.drawPills(colors, height * 0.15, { categories: true });
|
|
this.renderCategories(ctx, colors, width, height);
|
|
} else if (
|
|
["categories_boxes", "categories_boxes_with_topics"].includes(
|
|
this.get("step.fieldsById.homepage_style.value")
|
|
)
|
|
) {
|
|
this.drawPills(colors, height * 0.15, { categories: true });
|
|
const topics =
|
|
this.get("step.fieldsById.homepage_style.value") ===
|
|
"categories_boxes_with_topics";
|
|
this.renderCategoriesBoxes(ctx, colors, width, height, { topics });
|
|
} else {
|
|
this.drawPills(colors, height * 0.15, { categories: true });
|
|
this.renderCategoriesWithTopics(ctx, colors, width, height);
|
|
}
|
|
},
|
|
|
|
renderCategoriesBoxes(ctx, colors, 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 'Arial'`;
|
|
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 'Arial'`;
|
|
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 'Arial'`;
|
|
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, 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 'Arial'`;
|
|
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 'Arial'`;
|
|
ctx.fillStyle = colors.primary;
|
|
ctx.fillText(category.name, cols[0], textPos);
|
|
|
|
ctx.font = `${bodyFontSize * 0.8}em 'Arial'`;
|
|
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 'Arial'`;
|
|
ctx.fillText(
|
|
Math.floor(Math.random() * 90) + 10,
|
|
cols[1] + 15,
|
|
textPos
|
|
);
|
|
} else {
|
|
ctx.font = `${bodyFontSize}em 'Arial'`;
|
|
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 'Arial'`;
|
|
const textPos = y + topicHeight * 0.35;
|
|
ctx.fillStyle = colors.tertiary;
|
|
ctx.fillText(`${title}`, cols[2], textPos);
|
|
y += topicHeight;
|
|
});
|
|
}
|
|
},
|
|
|
|
renderCategoriesWithTopics(ctx, colors, 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 'Arial'`;
|
|
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 'Arial'`;
|
|
ctx.fillStyle = colors.primary;
|
|
ctx.fillText(category.name, cols[0], textPos);
|
|
|
|
ctx.font = `${bodyFontSize * 0.8}em 'Arial'`;
|
|
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 'Arial'`;
|
|
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 'Arial'`;
|
|
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 'Arial'`;
|
|
ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[4], textPos);
|
|
ctx.font = `${bodyFontSize}em 'Arial'`;
|
|
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 'Arial'`;
|
|
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, width, height) {
|
|
const rowHeight = height / 6.6;
|
|
const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50);
|
|
const bodyFontSize = height / 440.0;
|
|
|
|
ctx.font = `${bodyFontSize}em 'Arial'`;
|
|
|
|
const margin = height * 0.03;
|
|
|
|
const drawLine = y => {
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = 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 'Arial'`;
|
|
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 'Arial'`;
|
|
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 'Arial'`;
|
|
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 'Arial'`;
|
|
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();
|
|
});
|
|
}
|
|
});
|