Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CSUI multiple inline and overlay anchors #260

Merged
merged 15 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/create-plasmo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-plasmo",
"version": "0.56.1",
"version": "0.57.0-alpha.1",
"description": "Create Plasmo Framework Browser Extension",
"main": "dist/index.js",
"bin": "dist/index.js",
Expand Down
6 changes: 3 additions & 3 deletions cli/plasmo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "plasmo",
"version": "0.56.1",
"version": "0.57.0-alpha.1",
"description": "The Plasmo Platform CLI",
"main": "dist/index.js",
"types": "dist/type.d.ts",
Expand Down Expand Up @@ -42,14 +42,14 @@
"@plasmohq/parcel-config": "workspace:*",
"archiver": "5.3.1",
"buffer": "6.0.3",
"chalk": "5.1.0",
"chalk": "5.1.2",
"change-case": "4.1.2",
"dotenv": "16.0.3",
"dotenv-expand": "9.0.0",
"events": "3.3.0",
"fflate": "0.7.4",
"get-port": "6.1.2",
"got": "12.5.1",
"got": "12.5.2",
"inquirer": "9.1.3",
"is-path-inside": "4.0.0",
"mnemonic-id": "3.2.7",
Expand Down
7 changes: 4 additions & 3 deletions cli/plasmo/src/features/extension-devtools/common-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { getBundleConfig } from "./get-bundle-config"

export const getCommonPath = (
projectDirectory = cwd(),
{ target } = getBundleConfig(),
dotPlasmo = ".plasmo"
{ target } = getBundleConfig()
) => {
process.env.PLASMO_PROJECT_DIR = projectDirectory

const packageName = basename(projectDirectory)

process.env.PLASMO_SRC_PATH =
Expand Down Expand Up @@ -38,7 +39,7 @@ export const getCommonPath = (

const distDirectory = resolve(buildDirectory, distDirectoryName)

const dotPlasmoDirectory = resolve(projectDirectory, dotPlasmo)
const dotPlasmoDirectory = resolve(projectDirectory, ".plasmo")

const cacheDirectory = resolve(dotPlasmoDirectory, "cache")

Expand Down
15 changes: 6 additions & 9 deletions cli/plasmo/src/features/manifest-factory/create-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,27 @@ export async function createManifest(bundleConfig: PlasmoBundleConfig) {
const contentIndex = contentIndexList.find(existsSync)
const backgroundIndex = backgroundIndexList.find(existsSync)

const hasEntrypoints = await Promise.all([
plasmoManifest.scaffolder.initTemplateFiles("popup"),
plasmoManifest.scaffolder.initTemplateFiles("options"),
plasmoManifest.scaffolder.initTemplateFiles("newtab"),
plasmoManifest.scaffolder.initTemplateFiles("devtools"),
const initResults = await Promise.all([
plasmoManifest.scaffolder.init(),
plasmoManifest.toggleContentScript(contentIndex, true),
plasmoManifest.toggleBackground(backgroundIndex, true),
plasmoManifest.addContentScriptsDirectory(),
plasmoManifest.addTabsDirectory()
])

const hasEntrypoints = initResults.flat()

if (!hasEntrypoints.includes(true)) {
wLog(
"Unable to find any entrypoints. You may end up with an empty extension..."
)
wLog("Unable to find any entry files. The extension might be empty")
}

const [hasPopup, hasOptions, hasNewtab, hasDevtools] = hasEntrypoints

plasmoManifest
.togglePopup(hasPopup)
.toggleOptions(hasOptions)
.toggleDevtools(hasDevtools)
.toggleNewtab(hasNewtab)
.toggleDevtools(hasDevtools)

await plasmoManifest.write(true)

Expand Down
30 changes: 28 additions & 2 deletions cli/plasmo/src/features/manifest-factory/scaffolder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { existsSync } from "fs"
import { ensureDir } from "fs-extra"
import { copy, ensureDir } from "fs-extra"
import { readFile, writeFile } from "fs/promises"
import { ParsedPath, join, relative, resolve } from "path"

Expand Down Expand Up @@ -31,7 +31,33 @@ export class Scaffolder {
this.#plasmoManifest = plasmoManifest
}

initTemplateFiles = async (uiPageName: ExtensionUIPage) => {
async init() {
const [_, ...uiPagesResult] = await Promise.all([
this.#copyStaticCommon(),
this.#initUiPageTemplate("popup"),
this.#initUiPageTemplate("options"),
this.#initUiPageTemplate("newtab"),
this.#initUiPageTemplate("devtools")
])

return uiPagesResult
}

#copyStaticCommon = async () => {
const templateCommonDirectory = resolve(
this.#plasmoManifest.templatePath.staticTemplatePath,
"common"
)

const staticCommonDirectory = resolve(
this.commonPath.staticDirectory,
"common"
)

return copy(templateCommonDirectory, staticCommonDirectory)
}

#initUiPageTemplate = async (uiPageName: ExtensionUIPage) => {
vLog(`Creating static templates for ${uiPageName}`)

const indexList = this.projectPath[`${uiPageName}IndexList`]
Expand Down
87 changes: 70 additions & 17 deletions cli/plasmo/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,103 @@ export type PlasmoContentScript = Omit<Partial<ManifestContentScript>, "js">

type Async<T> = Promise<T> | T

type Getter<T> = () => Async<T>
type Getter<T, P = any> = (props?: P) => Async<T>

type GetHtmlElement = Getter<HTMLElement>
type GetElement = Getter<Element>

export type PlasmoGetRootContainer = GetHtmlElement
export type PlasmoGetOverlayAnchor = GetHtmlElement
export type PlasmoCSUIAnchor = {
element: Element
type: "overlay" | "inline"
}

export type PlasmoGetInlineAnchor = () => HTMLElement | null
export type PlasmoCSUIProps = {
anchor?: PlasmoCSUIAnchor
}

export type PlasmoCSUIMountState = {
document: Document
observer: MutationObserver | null
shadowHost: Element | null
inlineAnchor: Element | null

isMounting: boolean
isMutated: boolean
/**
* Used to quickly check if element is already mounted
*/
hostSet: Set<Element>

/**
* Used to add more metadata to the host Set
*/
hostMap: WeakMap<Element, PlasmoCSUIAnchor>

/**
* Used to align overlay anchor with elements on the page
*/
overlayTargetList: Element[]
}

export type PlasmoMountShadowHost = (
mountState: PlasmoCSUIMountState
) => Async<void>
export type PlasmoGetRootContainer = (
props: {
mountState?: PlasmoCSUIMountState
} & PlasmoCSUIProps
) => Async<Element>

export type PlasmoRender = (
createRootContainer: GetHtmlElement,
MountContainer: () => JSX.Element | HTMLElement
export type PlasmoGetOverlayAnchor = GetElement
export type PlasmoGetOverlayAnchorList = Getter<NodeList>

export type PlasmoGetInlineAnchor = GetElement
export type PlasmoGetInlineAnchorList = Getter<NodeList>

export type PlasmoMountShadowHost = (
props: {
observer: MutationObserver | null
shadowHost: Element
} & PlasmoCSUIProps
) => Async<void>

export type PlasmoGetShadowHostId = Getter<string>
export type PlasmoGetShadowHostId = Getter<string, PlasmoCSUIAnchor>

export type PlasmoGetStyle = Getter<HTMLStyleElement>
export type PlasmoGetStyle = Getter<HTMLStyleElement, PlasmoCSUIAnchor>

/**
* @return a cleanup unwatch function that will be run when unmounted
*/
export type PlasmoWatchOverlayAnchor = (
updatePosition: () => Promise<void>
) => void
) => () => void

export type PlasmoCSUIContainerProps = {
id?: string
children?: React.ReactNode
watchOverlayAnchor?: PlasmoWatchOverlayAnchor
} & PlasmoCSUIProps

export type PlasmoCSUIContainer = (
p: PlasmoCSUIContainerProps
) => JSX.Element | Element

export type PlasmoCreateShadowRoot = (
shadowHost: HTMLDivElement
shadowHost: HTMLElement
) => Async<ShadowRoot>

export type PlasmoRender = (
props: {
createRootContainer?: (p: PlasmoCSUIAnchor) => Async<Element>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the first parameter need to be required? I was wondering how we'd use this if we're not mounting into multiple elements, like in the following example.

https://github.com/PlasmoHQ/examples/blob/b3f699d537c5143c928ede46586901bff1cfca62/with-content-scripts-ui/contents/plasmo-root-container.tsx

} & PlasmoCSUIProps,
InlineCSUIContainer?: PlasmoCSUIContainer,
OverlayCSUIContainer?: PlasmoCSUIContainer
) => Async<void>

export type PlasmoCSUI = {
default: any
getStyle: PlasmoGetStyle
getShadowHostId: PlasmoGetShadowHostId

getOverlayAnchor: PlasmoGetOverlayAnchor
getOverlayAnchorList: PlasmoGetOverlayAnchorList

getInlineAnchor: PlasmoGetInlineAnchor
getInlineAnchorList: PlasmoGetInlineAnchorList

getRootContainer: PlasmoGetRootContainer

Expand Down
69 changes: 69 additions & 0 deletions cli/plasmo/templates/static/common/csui-container-react.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react"

import type { PlasmoCSUIContainerProps } from "~type"

export const OverlayCSUIContainer = (props: PlasmoCSUIContainerProps) => {
const [top, setTop] = React.useState(0)
const [left, setLeft] = React.useState(0)

React.useEffect(() => {
// Handle overlay repositioning
if (props.anchor.type !== "overlay") {
return
}

const updatePosition = async () => {
const rect = props.anchor.element.getBoundingClientRect()

if (!rect) {
return
}

const pos = {
left: rect.left + window.scrollX,
top: rect.top + window.scrollY
}

setLeft(pos.left)
setTop(pos.top)
}

updatePosition()

const unwatch = props.watchOverlayAnchor?.(updatePosition)
window.addEventListener("scroll", updatePosition)

return () => {
unwatch?.()
window.removeEventListener("scroll", updatePosition)
}
}, [])

return (
<div
id={props.id}
className="plasmo-csui-container"
style={{
display: "flex",
position: "absolute",
top,
left
}}>
{props.children}
</div>
)
}

export const InlineCSUIContainer = (props: PlasmoCSUIContainerProps) => (
<div
id="plasmo-inline"
className="plasmo-csui-container"
style={{
display: "flex",
position: "relative",
top: 0,
left: 0
}}>
{props.children}
</div>
)
54 changes: 54 additions & 0 deletions cli/plasmo/templates/static/common/csui-container-vanilla.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { PlasmoCSUIContainerProps } from "~type"

export const createOverlayCSUIContainer = (props: PlasmoCSUIContainerProps) => {
const container = document.createElement("div")
container.className = "plasmo-csui-container"
container.id = props.id

container.style.cssText = `
display: flex;
position: relative;
top: 0px;
left: 0px;
`

if (props.anchor.type === "overlay") {
const updatePosition = async () => {
const rect = props.anchor.element.getBoundingClientRect()

if (!rect) {
return
}

const pos = {
left: rect.left + window.scrollX,
top: rect.top + window.scrollY
}

container.style.top = `${pos.top}px`
container.style.left = `${pos.left}px`
}

updatePosition()

props.watchOverlayAnchor?.(updatePosition)
window.addEventListener("scroll", updatePosition)
}

return container
}

export const createInlineCSUIContainer = (props: PlasmoCSUIContainerProps) => {
const container = document.createElement("div")
container.className = "plasmo-csui-container"
container.id = "plasmo-inline"

container.style.cssText = `
display: flex;
position: relative;
top: 0px;
left: 0px;
`

return container
}
Loading