discourse/app/assets/javascripts/wizard/addon/components/styling-preview.js
Dan Gebhardt 03b7b7d1bc
DEV: Remove usage of {{action}} modifiers - Take 2 (#18476)
This PR enables the [`no-action-modifiers`](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-action-modifiers.md) template lint rule and removes all usages of the `{{action}}` modifier in core.

In general, instances of `{{action "x"}}` have been replaced with `{{on "click" (action "x")}}`. 

In many cases, such as for `a` elements, we also need to prevent default event handling to avoid unwanted side effects. While the `{{action}}` modifier internally calls `event.preventDefault()`, we need to handle these cases more explicitly. For this purpose, this PR also adds the [ember-event-helpers](https://github.com/buschtoens/ember-event-helpers) dependency so we can use the `prevent-default` handler. For instance:

```
<a href {{on "click" (prevent-default (action "x"))}}>Do X</a>
```

Note that `action` has not in general been refactored away as a helper yet. In general, all event handlers should be methods on the corresponding component and referenced directly (e.g. `{{on "click" this.doSomething}}`). However, the `action` helper is used extensively throughout the codebase and often references methods in the `actions` hash on controllers or routes. Thus this refactor will also be extensive and probably deserves a separate PR.

Note: This work was done to complement #17767 by minimizing the potential impact of the `action` modifier override, which uses private API and arguably should be replaced with an AST transform.

This is a followup to #18333, which had to be reverted because it did not account for the default treatment of modifier keys by the {{action}} modifier.

Commits:
* Enable `no-action-modifiers` template lint rule
* Replace {{action "x"}} with {{on "click" (action "x")}}
* Remove unnecessary action helper usage
* Remove ctl+click tests for user-menu
   These tests now break in Chrome when used with addEventListener. As per the comment, they can probably be safely removed.
* Prevent default event handlers to avoid unwanted side effects
   Uses `event.preventDefault()` in event handlers to prevent default event handling. This had been done automatically by the `action` modifier, but is not always desirable or necessary.
* Restore UserCardContents#showUser action to avoid regression
   By keeping the `showUser` action, we can avoid a breaking change for plugins that rely upon it, while not interfering with the `showUser` argument that's been passed.
* Revert EditCategoryTab#selectTab -> EditCategoryTab#select
   Avoid potential breaking change in themes / plugins
* Restore GroupCardContents#showGroup action to avoid regression
   By keeping the `showGroup` action, we can avoid a breaking change for plugins that rely upon it, while not interfering with the `showGroup` argument that's been passed.
* Restore SecondFactorAddTotp#showSecondFactorKey action to avoid regression
   By keeping the `showSecondFactorKey` action, we can avoid a breaking change for plugins that rely upon it, while not interfering with the `showSecondFactorKey` property that's maintained on the controller.
* Refactor away from `actions` hash in ChooseMessage component
* Modernize EmojiPicker#onCategorySelection usage
* Modernize SearchResultEntry#logClick usage
* Modernize Discovery::Categories#showInserted usage
* Modernize Preferences::Account#resendConfirmationEmail usage
* Modernize MultiSelect::SelectedCategory#onSelectedNameClick usage
* Favor fn over action in SelectedChoice component
* Modernize WizardStep event handlers
* Favor fn over action usage in buttons
* Restore Login#forgotPassword action to avoid possible regression
* Introduce modKeysPressed utility
   Returns an array of modifier keys that are pressed during a given `MouseEvent` or `KeyboardEvent`.
* Don't interfere with click events on links with `href` values when modifier keys are pressed
2022-10-05 13:08:54 +01:00

219 lines
5.4 KiB
JavaScript

import {
chooseDarker,
createPreviewComponent,
darkLightDiff,
} from "wizard/lib/preview";
import I18n from "I18n";
import { bind, observes } from "discourse-common/utils/decorators";
import { action } from "@ember/object";
const LOREM = `
Lorem ipsum dolor sit amet, consectetur adipiscing.
Nullam eget sem non elit tincidunt rhoncus. Fusce
velit nisl, porttitor sed nisl ac, consectetur interdum
metus. Fusce in consequat augue, vel facilisis felis.`;
export default createPreviewComponent(628, 322, {
logo: null,
avatar: null,
previewTopic: true,
draggingActive: false,
startX: 0,
scrollLeft: 0,
init() {
this._super(...arguments);
this.wizard.on("homepageStyleChanged", this.onHomepageStyleChange);
},
willDestroy() {
this._super(...arguments);
this.wizard.off("homepageStyleChanged", this.onHomepageStyleChange);
},
didInsertElement() {
this._super(...arguments);
this.element.addEventListener("mouseleave", this.handleMouseLeave);
this.element.addEventListener("mousemove", this.handleMouseMove);
},
willDestroyElement() {
this._super(...arguments);
this.element.removeEventListener("mouseleave", this.handleMouseLeave);
this.element.removeEventListener("mousemove", this.handleMouseMove);
},
mouseDown(e) {
const slider = this.element.querySelector(".previews");
this.setProperties({
draggingActive: true,
startX: e.pageX - slider.offsetLeft,
scrollLeft: slider.scrollLeft,
});
},
@bind
handleMouseLeave() {
this.set("draggingActive", false);
},
mouseUp() {
this.set("draggingActive", false);
},
@bind
handleMouseMove(e) {
if (!this.draggingActive) {
return;
}
e.preventDefault();
const slider = this.element.querySelector(".previews"),
x = e.pageX - slider.offsetLeft,
walk = (x - this.startX) * 1.5;
slider.scrollLeft = this.scrollLeft - walk;
if (slider.scrollLeft < 50) {
this.set("previewTopic", true);
}
if (slider.scrollLeft > slider.offsetWidth) {
this.set("previewTopic", false);
}
},
didUpdateAttrs() {
this._super(...arguments);
this.triggerRepaint();
},
@bind
onHomepageStyleChange() {
this.set("previewTopic", false);
},
@observes("previewTopic")
scrollPreviewArea() {
const el = this.element.querySelector(".previews");
el.scrollTo({
top: 0,
left: this.previewTopic ? 0 : el.scrollWidth - el.offsetWidth,
behavior: "smooth",
});
},
images() {
return {
logo: this.wizard.getLogoUrl(),
avatar: "/images/wizard/trout.png",
};
},
paint({ ctx, colors, font, headingFont, width, height }) {
const headerHeight = height * 0.3;
this.drawFullHeader(colors, headingFont, this.logo);
const margin = 20;
const avatarSize = height * 0.15;
const lineHeight = height / 14;
// Draw a fake topic
this.scaleImage(
this.avatar,
margin,
headerHeight + height * 0.09,
avatarSize,
avatarSize
);
const titleFontSize = headerHeight / 55;
ctx.beginPath();
ctx.fillStyle = colors.primary;
ctx.font = `bold ${titleFontSize}em '${headingFont}'`;
ctx.fillText(I18n.t("wizard.previews.topic_title"), margin, height * 0.3);
const bodyFontSize = height / 330.0;
ctx.font = `${bodyFontSize}em '${font}'`;
let line = 0;
const lines = LOREM.split("\n");
for (let i = 0; i < 5; i++) {
line = height * 0.35 + i * lineHeight;
ctx.fillText(lines[i], margin + avatarSize + margin, line);
}
// Share Button
const shareButtonWidth = I18n.t("wizard.previews.share_button").length * 11;
ctx.beginPath();
ctx.rect(margin, line + lineHeight, shareButtonWidth, height * 0.1);
// accounts for hard-set color variables in solarized themes
ctx.fillStyle =
colors.primary_low ||
darkLightDiff(colors.primary, colors.secondary, 90, 65);
ctx.fillStyle = chooseDarker(colors.primary, colors.secondary);
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(
I18n.t("wizard.previews.share_button"),
margin + 10,
line + lineHeight * 1.9
);
// Reply Button
const replyButtonWidth = I18n.t("wizard.previews.reply_button").length * 11;
ctx.beginPath();
ctx.rect(
shareButtonWidth + margin + 10,
line + lineHeight,
replyButtonWidth,
height * 0.1
);
ctx.fillStyle = colors.tertiary;
ctx.fill();
ctx.fillStyle = colors.secondary;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(
I18n.t("wizard.previews.reply_button"),
shareButtonWidth + margin + 20,
line + lineHeight * 1.9
);
// Draw Timeline
const timelineX = width * 0.86;
ctx.beginPath();
ctx.strokeStyle = colors.tertiary;
ctx.lineWidth = 0.5;
ctx.moveTo(timelineX, height * 0.3);
ctx.lineTo(timelineX, height * 0.7);
ctx.stroke();
// Timeline
ctx.beginPath();
ctx.strokeStyle = colors.tertiary;
ctx.lineWidth = 2;
ctx.moveTo(timelineX, height * 0.3);
ctx.lineTo(timelineX, height * 0.4);
ctx.stroke();
ctx.font = `Bold ${bodyFontSize}em ${font}`;
ctx.fillStyle = colors.primary;
ctx.fillText("1 / 20", timelineX + margin, height * 0.3 + margin * 1.5);
},
@action
setPreviewHomepage(event) {
event?.preventDefault();
this.set("previewTopic", false);
},
@action
setPreviewTopic(event) {
event?.preventDefault();
this.set("previewTopic", true);
},
});