Skip to content

Commit

Permalink
#7665: add a text selection popover behind skunkworks flag (#7666)
Browse files Browse the repository at this point in the history
  • Loading branch information
twschiller authored Feb 19, 2024
1 parent c06f703 commit a889205
Show file tree
Hide file tree
Showing 17 changed files with 924 additions and 20 deletions.
4 changes: 4 additions & 0 deletions src/__snapshots__/Storyshots.test.js.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 1 addition & 17 deletions src/bricks/transformers/temporaryInfo/popoverUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,7 @@ import { setTemporaryOverlayPanel } from "@/contentScript/ephemeralPanelControll
import { getThisFrame } from "webext-messenger";
import { html } from "code-tag";
import { setAnimationFrameInterval } from "@/utils/domUtils";

/**
* Attaches a tooltip container to the DOM.
*
* Having a separate container instead of attaching to the body directly improves performance, see:
* https://popper.js.org/docs/v2/performance/#attaching-elements-to-the-dom
*/
function ensureTooltipsContainer(): Element {
let container = document.querySelector("#pb-tooltips-container");
if (!container) {
container = document.createElement("div");
container.id = "pb-tooltips-container";
document.body.append(container);
}

return container;
}
import { ensureTooltipsContainer } from "@/contentScript/tooltipDom";

// https://popper.js.org/docs/v2/constructors/
export type Placement =
Expand Down
68 changes: 68 additions & 0 deletions src/contentScript/selectionTooltip/ActionRegistry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import ActionRegistry from "@/contentScript/selectionTooltip/ActionRegistry";
import { uuidv4 } from "@/types/helpers";

describe("actionRegistry", () => {
it("sets emoji from title", () => {
const componentId = uuidv4();
const registry = new ActionRegistry();

registry.register(componentId, {
title: "👋 Hello",
icon: null,
handler() {},
});

expect(registry.actions.get(componentId)?.emoji).toBe("👋");
});

it("defaults icon to box", () => {
const componentId = uuidv4();
const registry = new ActionRegistry();

registry.register(componentId, {
title: "Hello",
icon: null,
handler() {},
});

expect(registry.actions.get(componentId)?.icon).toStrictEqual({
id: "box",
library: "bootstrap",
});
});

it("fires event listener on register and unregister", () => {
const componentId = uuidv4();
const registry = new ActionRegistry();
const listener = jest.fn();

registry.onChange.add(listener);
registry.register(componentId, {
title: "Hello",
icon: null,
handler() {},
});

expect(listener).toBeCalledTimes(1);

registry.unregister(componentId);
expect(listener).toBeCalledTimes(2);
});
});
80 changes: 80 additions & 0 deletions src/contentScript/selectionTooltip/ActionRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import type { UUID } from "@/types/stringTypes";
import type { IconConfig } from "@/types/iconTypes";
import { splitStartingEmoji } from "@/utils/stringUtils";
import { SimpleEventTarget } from "@/utils/SimpleEventTarget";
import type { Nullishable } from "@/utils/nullishUtils";

type TextSelectionAction = {
// NOTE: currently there's no way to set icons for context menu items, so this will always be null
icon?: Nullishable<IconConfig>;
title: string;
handler: (text: string) => void;
};

export type RegisteredAction = TextSelectionAction & {
emoji: Nullishable<string>;
};

const defaultIcon: IconConfig = {
id: "box",
library: "bootstrap",
};

/**
* Registry for text selection actions.
* @since 1.8.10
*/
class ActionRegistry {
/**
* Map from component UUID to registered action
*/
public readonly actions = new Map<UUID, RegisteredAction>();

/**
* Event fired when the set of registered actions changes
*/
public readonly onChange = new SimpleEventTarget<RegisteredAction[]>();

/**
* Register a new text selection action. Overwrites any existing action for the mod component.
* @param componentId the mod component id
* @param action the action definition
*/
register(componentId: UUID, action: TextSelectionAction): void {
const { startingEmoji } = splitStartingEmoji(action.title);
this.actions.set(componentId, {
...action,
emoji: startingEmoji,
icon: action.icon ?? defaultIcon,
});
this.onChange.emit([...this.actions.values()]);
}

/**
* Unregister a text selection action. Does nothing if an action for the component is not registered.
* @param componentId the mod component id
*/
unregister(componentId: UUID): void {
this.actions.delete(componentId);
this.onChange.emit([...this.actions.values()]);
}
}

export default ActionRegistry;
54 changes: 54 additions & 0 deletions src/contentScript/selectionTooltip/SelectionToolbar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*!
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

@import "@/themes/colors.scss";

.toolbar {
display: flex;

height: 30px;
font-size: 16px;
//line-height: 20px;

padding-left: 3px;
padding-right: 3px;
}

.toolbarItem {
background-color: $S0;
color: black;
border-radius: 0;
border: 0;
cursor: pointer;

vertical-align: middle;
display: flex;
align-items: center;
justify-content: center;

&:hover {
background-color: $P100;
}

&:active {
background-color: $P300;
}
}

svg {
vertical-align: middle;
}
66 changes: 66 additions & 0 deletions src/contentScript/selectionTooltip/SelectionToolbar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import type { ComponentMeta, ComponentStory } from "@storybook/react";
import React from "react";
import SelectionToolbar from "@/contentScript/selectionTooltip/SelectionToolbar";
import ActionRegistry from "@/contentScript/selectionTooltip/ActionRegistry";
import { uuidv4 } from "@/types/helpers";
import { action } from "@storybook/addon-actions";

export default {
title: "Enhancements/SelectionToolbar",
component: SelectionToolbar,
} as ComponentMeta<typeof SelectionToolbar>;

const Template: ComponentStory<typeof SelectionToolbar> = (args) => (
<SelectionToolbar {...args} />
);

const emojiAction = {
title: "😊 Emoji",
handler() {
action("😊");
},
};

const textAction = {
title: "No Emoji",
handler() {
action("No Emoji");
},
};

const emojiRegistry = new ActionRegistry();
emojiRegistry.register(uuidv4(), emojiAction);
emojiRegistry.register(uuidv4(), emojiAction);

const mixedRegistry = new ActionRegistry();
mixedRegistry.register(uuidv4(), emojiAction);
mixedRegistry.register(uuidv4(), textAction);

export const EmojiButtons = Template.bind({});
EmojiButtons.args = {
registry: emojiRegistry,
onHide: action("onHide"),
};

export const MixedButtons = Template.bind({});
MixedButtons.args = {
registry: mixedRegistry,
onHide: action("onHide"),
};
Loading

0 comments on commit a889205

Please sign in to comment.