Merge branch 'main' of github.com:webstudio-is/webstudio-designer into redesign-assets

This commit is contained in:
Sara Vieira committed 2022-08-31 15:01:06 +03:00
commit d54b56e299
68 files changed
+564 -492

No files matched your search

+21 -11
View File
@@ -1,9 +1,16 @@
name: 'Release NPM package'
description: 'Release NPM package'
inputs:
package:
description: 'Package.json location (relative)'
default: 'package.json'
skip-build:
description: 'Skip build phase (useful for only configuration files)'
default: ''
filter:
description: 'Filter build phase by turbo'
default: ''
required: true
workdir:
description: 'Work directory for NPM publish'
default: '.'
required: true
token:
description: 'NPM Token'
@@ -16,13 +23,16 @@ runs:
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install Dependencies
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies and build 🔧
run: yarn install
shell: bash
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ inputs.token }}
dry-run: false
check-version: true
access: 'public'
package: ${{ inputs.package }}
- name: Build 🔧
if: ${{ inputs.skip-build == '' }}
run: yarn build --filter ${{ inputs.filter }}
shell: bash
- name: Publish package on NPM 📦
run: npm publish -w ${{ inputs.workdir }} --access public
shell: bash
env:
NODE_AUTH_TOKEN: ${{ inputs.token }}
+17
View File
@@ -0,0 +1,17 @@
name: Asset Uploader
on:
push:
paths:
- "packages/asset-uploader/**/*"
- ".github/workflows/asset-uploader.yml"
- ".github/actions/publish/action.yaml"
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/publish
with:
workdir: ./packages/asset-uploader
filter: "@webstudio-is/asset-uploader"
token: ${{ secrets.NPM_TOKEN }}
+3 -2
View File
@@ -1,4 +1,4 @@
name: Design System Publish
name: Design System
on:
push:
paths:
@@ -12,5 +12,6 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/publish
with:
package: "./packages/design-system/package.json"
workdir: ./packages/design-system
filter: "@webstudio-is/design-system"
token: ${{ secrets.NPM_TOKEN }}
+4 -2
View File
@@ -1,4 +1,4 @@
name: Eslint Config Publish
name: Eslint Config
on:
push:
paths:
@@ -12,5 +12,7 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/publish
with:
package: "./packages/eslint-config-custom/package.json"
skip-build: true
workdir: ./packages/eslint-config-custom
filter: "@webstudio-is/eslint-config-custom"
token: ${{ secrets.NPM_TOKEN }}
+17
View File
@@ -0,0 +1,17 @@
name: HTTP Client
on:
push:
paths:
- "packages/http-client/**/*"
- ".github/workflows/http-client.yml"
- ".github/actions/publish/action.yaml"
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/publish
with:
workdir: ./packages/http-client
filter: "@webstudio-is/http-client"
token: ${{ secrets.NPM_TOKEN }}
+3 -2
View File
@@ -1,4 +1,4 @@
name: Icons Publish
name: Icons
on:
push:
paths:
@@ -12,5 +12,6 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/publish
with:
package: "./packages/icons/package.json"
workdir: ./packages/icons
filter: "@webstudio-is/icons"
token: ${{ secrets.NPM_TOKEN }}
+4 -2
View File
@@ -1,4 +1,4 @@
name: Jest Config Publish
name: Jest Config
on:
push:
paths:
@@ -12,5 +12,7 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/publish
with:
package: "./packages/jest-config/package.json"
skip-build: true
workdir: ./packages/jest-config
filter: "@webstudio-is/jest-config"
token: ${{ secrets.NPM_TOKEN }}
+3 -2
View File
@@ -1,4 +1,4 @@
name: SDK publish
name: React SDK
on:
push:
paths:
@@ -12,5 +12,6 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/publish
with:
package: "./packages/react-sdk/package.json"
workdir: ./packages/react-sdk
filter: "@webstudio-is/react-sdk"
token: ${{ secrets.NPM_TOKEN }}
+4 -2
View File
@@ -1,4 +1,4 @@
name: Tsconfig Publish
name: Tsconfig
on:
push:
paths:
@@ -12,5 +12,7 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/publish
with:
package: "./packages/tsconfig/package.json"
skip-build: true
workdir: ./packages/tsconfig
filter: "@webstudio-is/tsconfig"
token: ${{ secrets.NPM_TOKEN }}
+2 -2
View File
@@ -7,9 +7,9 @@ import {
type Tree,
useAllUserProps,
globalStyles,
useSubscribe,
createElementsTree,
} from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useShortcuts } from "./shared/use-shortcuts";
import {
usePopulateRootInstance,
@@ -77,7 +77,7 @@ const useElementsTree = () => {
const useSubscribePreviewMode = () => {
const [isPreviewMode, setIsPreviewMode] = useState<boolean>(false);
useSubscribe<"previewMode", boolean>("previewMode", setIsPreviewMode);
useSubscribe("previewMode", setIsPreviewMode);
return isPreviewMode;
};
@@ -1,6 +1,5 @@
import {
type ChildrenUpdates,
publish,
useAllUserProps,
type Instance,
} from "@webstudio-is/react-sdk";
@@ -10,6 +9,13 @@ import { LexicalComposer, ContentEditable } from "./lexical";
import { TreeViewPlugin } from "./plugins/tree-view-plugin";
import { config } from "./config";
import Editor from "./editor";
import { publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
insertInlineInstance: Instance;
}
}
type ExampleTextEditorProps = {
onChange: (state: ChildrenUpdates) => void;
@@ -1,5 +1,6 @@
import { useEffect } from "react";
import { useSubscribe, type Instance } from "@webstudio-is/react-sdk";
import { type Instance } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { $createInstanceNode, InstanceNode } from "../nodes/node-instance";
import {
createCommand,
@@ -67,12 +68,9 @@ export const InstancePlugin = ({ children }: InstancePluginProps) => {
});
}, [editor, children]);
useSubscribe<"insertInlineInstance", Instance>(
"insertInlineInstance",
(payload) => {
editor.dispatchCommand<Instance>(INSERT_INSTANCE_COMMAND, payload);
}
);
useSubscribe("insertInlineInstance", (payload) => {
editor.dispatchCommand<Instance>(INSERT_INSTANCE_COMMAND, payload);
});
return null;
};
@@ -1,5 +1,4 @@
import { useCallback, useEffect, useRef } from "react";
import { publish } from "@webstudio-is/react-sdk";
import {
$isRangeSelection,
@@ -8,6 +7,13 @@ import {
COMMAND_PRIORITY_LOW,
useLexicalComposerContext,
} from "../lexical";
import { publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
selectionRect?: DOMRect;
}
}
export const ToolbarConnectorPlugin = () => {
const [editor] = useLexicalComposerContext();
@@ -16,7 +22,7 @@ export const ToolbarConnectorPlugin = () => {
const clearSelectionRect = () => {
if (lastSelectionRef.current) {
// Undefined Rect will hide toolbar
publish<"selectionRect">({ type: "selectionRect", payload: undefined });
publish({ type: "selectionRect", payload: undefined });
lastSelectionRef.current = undefined;
}
};
@@ -31,7 +37,7 @@ export const ToolbarConnectorPlugin = () => {
if (isTextSelected) {
const domRange = nativeSelection.getRangeAt(0);
const rect = domRange.getBoundingClientRect();
publish<"selectionRect", DOMRect>({
publish({
type: "selectionRect",
payload: rect,
});
@@ -3,9 +3,9 @@ import {
type Instance,
css as createCss,
type CSS,
useSubscribe,
toValue,
} from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { type StyleUpdates } from "~/shared/canvas-components";
type UseCssProps = {
@@ -23,12 +23,9 @@ const usePreviewCss = ({ instance, css }: UseCssProps) => {
StyleUpdates["updates"] | UpdatesReset
>([]);
useSubscribe<string, StyleUpdates>(
`previewStyle:${instance.id}`,
({ updates }) => {
setPreviewCss(updates);
}
);
useSubscribe(`previewStyle:${instance.id}`, ({ updates }) => {
setPreviewCss(updates);
});
// We are building a map for unsetting the ephemeral values we previously set for the preview
useEffect(() => {
+30 -41
View File
@@ -1,17 +1,14 @@
import { useEffect, useRef } from "react";
import store from "immerhin";
import {
type Breakpoint,
useSubscribe,
setBreakpoints,
publish,
} from "@webstudio-is/react-sdk";
import { type Breakpoint, setBreakpoints } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { deleteCssRulesByBreakpoint } from "~/shared/css-utils";
import {
breakpointsContainer,
rootInstanceContainer,
useBreakpoints,
} from "~/shared/nano-states";
import { publish } from "~/shared/pubsub";
export const useInitializeBreakpoints = (breakpoints: Array<Breakpoint>) => {
const [, setCurrentBreakpoints] = useBreakpoints();
@@ -27,7 +24,7 @@ export const useInitializeBreakpoints = (breakpoints: Array<Breakpoint>) => {
const usePublishBreakpoints = () => {
const [breakpoints] = useBreakpoints();
useEffect(() => {
publish<"loadBreakpoints", Array<Breakpoint>>({
publish({
type: "loadBreakpoints",
payload: breakpoints,
});
@@ -35,46 +32,38 @@ const usePublishBreakpoints = () => {
};
const useBreakpointChange = () => {
useSubscribe<"breakpointChange", Breakpoint>(
"breakpointChange",
(breakpoint) => {
store.createTransaction([breakpointsContainer], (breakpoints) => {
const foundBreakpoint = breakpoints.find(
({ id }) => id == breakpoint.id
);
if (foundBreakpoint) {
foundBreakpoint.label = breakpoint.label;
foundBreakpoint.minWidth = breakpoint.minWidth;
} else {
// Its a new breakpoint
breakpoints.push(breakpoint);
}
useSubscribe("breakpointChange", (breakpoint) => {
store.createTransaction([breakpointsContainer], (breakpoints) => {
const foundBreakpoint = breakpoints.find(({ id }) => id == breakpoint.id);
if (foundBreakpoint) {
foundBreakpoint.label = breakpoint.label;
foundBreakpoint.minWidth = breakpoint.minWidth;
} else {
// Its a new breakpoint
breakpoints.push(breakpoint);
}
setBreakpoints(breakpoints);
});
}
);
setBreakpoints(breakpoints);
});
});
};
const useBreakpointDelete = () => {
useSubscribe<"breakpointDelete", Breakpoint>(
"breakpointDelete",
(breakpoint) => {
store.createTransaction(
[breakpointsContainer, rootInstanceContainer],
(breakpoints, rootInstance) => {
if (rootInstance === undefined) return;
useSubscribe("breakpointDelete", (breakpoint) => {
store.createTransaction(
[breakpointsContainer, rootInstanceContainer],
(breakpoints, rootInstance) => {
if (rootInstance === undefined) return;
const index = breakpoints.findIndex(({ id }) => id == breakpoint.id);
if (index !== -1) {
breakpoints.splice(index, 1);
}
deleteCssRulesByBreakpoint(rootInstance, breakpoint.id);
const index = breakpoints.findIndex(({ id }) => id == breakpoint.id);
if (index !== -1) {
breakpoints.splice(index, 1);
}
);
}
);
deleteCssRulesByBreakpoint(rootInstance, breakpoint.id);
}
);
});
};
export const useHandleBreakpoints = () => {
@@ -2,11 +2,11 @@ import {
type InstanceProps,
type Instance,
allUserPropsContainer,
publish,
} from "@webstudio-is/react-sdk";
import { selectedInstanceContainer } from "./nano-states";
import { cloneInstance } from "~/shared/tree-utils";
import { cloneProps } from "~/shared/props-utils";
import { publish } from "~/shared/pubsub";
let currentInstance: Instance | undefined;
let currentProps: InstanceProps | undefined;
@@ -23,7 +23,7 @@ export const paste = () => {
const props = currentProps
? cloneProps(currentProps, { instanceId: instance.id })
: undefined;
publish<"insertInstance", { instance: Instance; props?: InstanceProps }>({
publish({
type: "insertInstance",
payload: {
instance,
+46 -55
View File
@@ -1,16 +1,14 @@
import { useCallback, useEffect, useMemo, useRef } from "react";
import ObjectId from "bson-objectid";
import {
type InstanceProps,
type Instance,
type Tree,
components,
allUserPropsContainer,
getBrowserStyle,
useAllUserProps,
useSubscribe,
publish,
} from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import {
deleteInstanceMutable,
populateInstance,
@@ -40,6 +38,18 @@ import {
} from "~/shared/nano-states";
import { useMeasure } from "~/shared/dom-hooks";
import { findInstanceByElement } from "~/shared/dom-utils";
import { publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
hoveredInstanceRect: DOMRect;
hoverInstance?: HoveredInstanceData;
loadRootInstance?: Instance;
selectedInstanceRect: DOMRect;
selectInstance?: SelectedInstanceData;
textEditingInstanceId?: Instance["id"];
}
}
export const usePopulateRootInstance = (tree: Tree) => {
// @todo ssr workaround for https://github.com/webstudio-is/webstudio-designer/issues/213
@@ -80,14 +90,7 @@ export const findInsertLocation = (
export const useInsertInstance = () => {
const [selectedInstance, setSelectedInstance] = useSelectedInstance();
useSubscribe<
"insertInstance",
{
instance: Instance;
dropTarget?: { parentId: Instance["id"]; position: number };
props?: InstanceProps;
}
>("insertInstance", ({ instance, dropTarget, props }) => {
useSubscribe("insertInstance", ({ instance, dropTarget, props }) => {
store.createTransaction(
[rootInstanceContainer, allUserPropsContainer],
(rootInstance, allUserProps) => {
@@ -112,13 +115,7 @@ export const useInsertInstance = () => {
export const useReparentInstance = () => {
const [selectedInstance, setSelectedInstance] = useSelectedInstance();
useSubscribe<
"reparentInstance",
{
instanceId: Instance["id"];
dropTarget: { instanceId: Instance["id"]; position: number | "end" };
}
>("reparentInstance", ({ instanceId, dropTarget }) => {
useSubscribe("reparentInstance", ({ instanceId, dropTarget }) => {
store.createTransaction([rootInstanceContainer], (rootInstance) => {
if (rootInstance === undefined) return;
reparentInstanceMutable(
@@ -144,40 +141,34 @@ export const useReparentInstance = () => {
export const useDeleteInstance = () => {
const [rootInstance] = useRootInstance();
const [selectedInstance, setSelectedInstance] = useSelectedInstance();
useSubscribe<"deleteInstance", { id: Instance["id"] }>(
"deleteInstance",
({ id }) => {
if (rootInstance !== undefined && selectedInstance !== undefined) {
// @todo tell user they can't delete root
if (id === rootInstance.id) {
return;
}
useSubscribe("deleteInstance", ({ id }) => {
if (rootInstance !== undefined && selectedInstance !== undefined) {
// @todo tell user they can't delete root
if (id === rootInstance.id) {
return;
}
const parentInstance = findParentInstance(rootInstance, id);
if (parentInstance !== undefined) {
const siblingInstance = findClosestSiblingInstance(
parentInstance,
id
);
setSelectedInstance(siblingInstance || parentInstance);
}
const parentInstance = findParentInstance(rootInstance, id);
if (parentInstance !== undefined) {
const siblingInstance = findClosestSiblingInstance(parentInstance, id);
setSelectedInstance(siblingInstance || parentInstance);
}
// @todo deleting instance should involve also deleting it's props
// If we don't delete them - they just live both on client and db
// Pros:
// - if we undo the deletion we don't need to undo the props deletion
// - in a multiplayer environment, some other user could have changed a prop while we have deleted the instance
// and then if we restore the instance, we would be restoring it with our props, potentially overwriting other users changes
// The way it is now it will actually still enable parallel deletion props editing and restoration.
// Contra: we are piling them up.
// Potentially we could also solve this by periodically removing unused props after while when instance was deleted
store.createTransaction([rootInstanceContainer], (rootInstance) => {
if (rootInstance !== undefined) {
deleteInstanceMutable(rootInstance, id);
}
});
}
);
// @todo deleting instance should involve also deleting it's props
// If we don't delete them - they just live both on client and db
// Pros:
// - if we undo the deletion we don't need to undo the props deletion
// - in a multiplayer environment, some other user could have changed a prop while we have deleted the instance
// and then if we restore the instance, we would be restoring it with our props, potentially overwriting other users changes
// The way it is now it will actually still enable parallel deletion props editing and restoration.
// Contra: we are piling them up.
// Potentially we could also solve this by periodically removing unused props after while when instance was deleted
store.createTransaction([rootInstanceContainer], (rootInstance) => {
if (rootInstance !== undefined) {
deleteInstanceMutable(rootInstance, id);
}
});
});
};
export const usePublishSelectedInstanceData = (treeId: Tree["id"]) => {
@@ -210,7 +201,7 @@ export const usePublishSelectedInstanceData = (treeId: Tree["id"]) => {
props,
};
}
publish<"selectInstance", SelectedInstanceData>({
publish({
type: "selectInstance",
payload,
});
@@ -227,7 +218,7 @@ export const usePublishHoveredInstanceData = () => {
component: instance.component,
}
: undefined;
publish<"hoverInstance", HoveredInstanceData | undefined>({
publish({
type: "hoverInstance",
payload,
});
@@ -237,7 +228,7 @@ export const usePublishHoveredInstanceData = () => {
export const usePublishRootInstance = () => {
const [rootInstance] = useRootInstance();
useEffect(() => {
publish<"loadRootInstance", Instance>({
publish({
type: "loadRootInstance",
payload: rootInstance,
});
@@ -245,7 +236,7 @@ export const usePublishRootInstance = () => {
};
const publishRect = (rect: DOMRect) => {
publish<"selectedInstanceRect", DOMRect>({
publish({
type: "selectedInstanceRect",
payload: rect,
});
@@ -269,7 +260,7 @@ export const usePublishHoveredInstanceRect = () => {
const [element] = useHoveredElement();
const publishRect = useCallback(() => {
if (element === undefined) return;
publish<"hoveredInstanceRect", DOMRect>({
publish({
type: "hoveredInstanceRect",
payload: element.getBoundingClientRect(),
});
@@ -321,7 +312,7 @@ export const useUnselectInstance = () => {
export const usePublishTextEditingInstanceId = () => {
const [editingInstanceId] = useTextEditingInstanceId();
useEffect(() => {
publish<"textEditingInstanceId", Instance["id"] | undefined>({
publish({
type: "textEditingInstanceId",
payload: editingInstanceId,
});
+8 -15
View File
@@ -1,9 +1,5 @@
import {
type UserPropsUpdates,
useSubscribe,
allUserPropsContainer,
DeleteProp,
} from "@webstudio-is/react-sdk";
import { allUserPropsContainer } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import store from "immerhin";
import {
updateAllUserPropsMutable,
@@ -11,16 +7,13 @@ import {
} from "~/shared/props-utils";
export const useManageProps = () => {
useSubscribe<"updateProps", UserPropsUpdates>(
"updateProps",
(userPropsUpdates) => {
store.createTransaction([allUserPropsContainer], (allUserProps) => {
updateAllUserPropsMutable(allUserProps, userPropsUpdates);
});
}
);
useSubscribe("updateProps", (userPropsUpdates) => {
store.createTransaction([allUserPropsContainer], (allUserProps) => {
updateAllUserPropsMutable(allUserProps, userPropsUpdates);
});
});
useSubscribe<"deleteProp", DeleteProp>("deleteProp", (deleteProp) => {
useSubscribe("deleteProp", (deleteProp) => {
store.createTransaction([allUserPropsContainer], (allUserProps) => {
deletePropMutable(allUserProps, deleteProp);
});
+12 -16
View File
@@ -1,25 +1,21 @@
import store from "immerhin";
import { useSubscribe } from "@webstudio-is/react-sdk";
import { type StyleUpdates } from "~/shared/canvas-components";
import { useSubscribe } from "~/shared/pubsub";
import { setInstanceStyleMutable } from "~/shared/tree-utils";
import { useSelectedInstance } from "./nano-states";
import { rootInstanceContainer } from "~/shared/nano-states";
export const useUpdateStyle = () => {
const [selectedInstance] = useSelectedInstance();
useSubscribe<"updateStyle", StyleUpdates>(
"updateStyle",
({ id, updates, breakpoint }) => {
// Only update styles if they match the selected instance
// It can potentially happen that we selected a difference instance right after we changed the style in style panel.
if (id !== selectedInstance?.id) return;
useSubscribe("updateStyle", ({ id, updates, breakpoint }) => {
// Only update styles if they match the selected instance
// It can potentially happen that we selected a difference instance right after we changed the style in style panel.
if (id !== selectedInstance?.id) return;
store.createTransaction([rootInstanceContainer], (rootInstance) => {
if (rootInstance === undefined) {
return;
}
setInstanceStyleMutable(rootInstance, id, updates, breakpoint);
});
}
);
store.createTransaction([rootInstanceContainer], (rootInstance) => {
if (rootInstance === undefined) {
return;
}
setInstanceStyleMutable(rootInstance, id, updates, breakpoint);
});
});
};
@@ -1,5 +1,11 @@
import { type SyncStatus } from "~/shared/sync";
import { publish } from "@webstudio-is/react-sdk";
import { publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
syncStatus: SyncStatus;
}
}
// @todo because of limitation with nested models and saving tree as a Json type
// we load that entire Json to manipulate and if a second request happens before the first one has written
@@ -22,13 +28,13 @@ const dequeue = () => {
const job = queue.shift();
if (job) {
isInProgress = true;
publish<"syncStatus", SyncStatus>({
publish({
type: "syncStatus",
payload: "syncing",
});
job().finally(() => {
isInProgress = false;
publish<"syncStatus", SyncStatus>({
publish({
type: "syncStatus",
payload: "idle",
});
@@ -21,13 +21,21 @@ import {
useDrop,
} from "@webstudio-is/design-system";
import {
publish,
useSubscribe,
type Instance,
components,
BaseInstance,
toBaseInstance,
} from "@webstudio-is/react-sdk";
import { publish, useSubscribe } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
dragEnd: DragEndPayload;
dragMove: DragMovePayload;
dragStart: DragStartPayload;
dropTargetChange: DropTargetChangePayload;
}
}
export type DropTargetChangePayload = {
rect: DropTarget<null>["rect"];
@@ -48,9 +56,12 @@ export type DragEndPayload = {
export type DragMovePayload = { canvasCoordinates: Point };
const initialState = {
dropTarget: undefined as DropTarget<Instance> | undefined,
dragItem: undefined as BaseInstance | undefined,
const initialState: {
dropTarget: DropTarget<Instance> | undefined;
dragItem: BaseInstance | undefined;
} = {
dropTarget: undefined,
dragItem: undefined,
};
export const useDragAndDrop = () => {
@@ -131,7 +142,7 @@ export const useDragAndDrop = () => {
onDropTargetChange(dropTarget) {
state.current.dropTarget = dropTarget;
publish<"dropTargetChange", DropTargetChangePayload>({
publish({
type: "dropTargetChange",
payload: {
rect: dropTarget.rect,
@@ -194,7 +205,7 @@ export const useDragAndDrop = () => {
autoScrollHandlers.setEnabled(true);
dropHandlers.handleStart();
publish<"dragStart", DragStartPayload>({
publish({
type: "dragStart",
payload: {
origin: "canvas",
@@ -210,7 +221,7 @@ export const useDragAndDrop = () => {
dropHandlers.handleEnd({ isCanceled });
autoScrollHandlers.setEnabled(false);
publish<"dragEnd", DragEndPayload>({
publish({
type: "dragEnd",
payload: { origin: "canvas", isCanceled },
});
@@ -218,13 +229,7 @@ export const useDragAndDrop = () => {
const { dropTarget, dragItem } = state.current;
if (dropTarget && dragItem && isCanceled === false) {
publish<
"reparentInstance",
{
instanceId: Instance["id"];
dropTarget: { instanceId: Instance["id"]; position: number };
}
>({
publish({
type: "reparentInstance",
payload: {
instanceId: dragItem.id,
@@ -263,55 +268,40 @@ export const useDragAndDrop = () => {
// Handle drag from the panel
// ================================================================
useSubscribe<"dragStart", DragStartPayload>(
"dragStart",
({ origin, dragItem }) => {
if (origin === "panel") {
state.current.dragItem = dragItem;
autoScrollHandlers.setEnabled(true);
dropHandlers.handleStart();
}
useSubscribe("dragStart", ({ origin, dragItem }) => {
if (origin === "panel") {
state.current.dragItem = dragItem;
autoScrollHandlers.setEnabled(true);
dropHandlers.handleStart();
}
);
});
useSubscribe<"dragMove", DragMovePayload>(
"dragMove",
({ canvasCoordinates }) => {
dropHandlers.handleMove(canvasCoordinates);
autoScrollHandlers.handleMove(canvasCoordinates);
}
);
useSubscribe("dragMove", ({ canvasCoordinates }) => {
dropHandlers.handleMove(canvasCoordinates);
autoScrollHandlers.handleMove(canvasCoordinates);
});
useSubscribe<"dragEnd", DragEndPayload>(
"dragEnd",
({ origin, isCanceled }) => {
if (origin === "panel") {
dropHandlers.handleEnd({ isCanceled });
autoScrollHandlers.setEnabled(false);
useSubscribe("dragEnd", ({ origin, isCanceled }) => {
if (origin === "panel") {
dropHandlers.handleEnd({ isCanceled });
autoScrollHandlers.setEnabled(false);
const { dropTarget, dragItem } = state.current;
const { dropTarget, dragItem } = state.current;
if (dropTarget && dragItem && isCanceled === false) {
publish<
"insertInstance",
{
instance: Instance;
dropTarget: { parentId: Instance["id"]; position: number };
}
>({
type: "insertInstance",
payload: {
instance: createInstance({ component: dragItem.component }),
dropTarget: {
parentId: dropTarget.data.id,
position: dropTarget.indexWithinChildren,
},
if (dropTarget && dragItem && isCanceled === false) {
publish({
type: "insertInstance",
payload: {
instance: createInstance({ component: dragItem.component }),
dropTarget: {
parentId: dropTarget.data.id,
position: dropTarget.indexWithinChildren,
},
});
}
state.current = { ...initialState };
},
});
}
state.current = { ...initialState };
}
);
});
};
@@ -1,13 +1,19 @@
import { publish } from "@webstudio-is/react-sdk";
import { useScrollState } from "~/shared/dom-hooks";
import { publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
scrollState: boolean;
}
}
export const usePublishScrollState = () => {
useScrollState({
onScrollStart() {
publish<"scrollState", boolean>({ type: "scrollState", payload: true });
publish({ type: "scrollState", payload: true });
},
onScrollEnd() {
publish<"scrollState", boolean>({
publish({
type: "scrollState",
payload: false,
});
@@ -1,35 +1,56 @@
import { useHotkeys } from "react-hotkeys-hook";
import store from "immerhin";
import {
type Instance,
publish,
useSubscribe,
components,
} from "@webstudio-is/react-sdk";
import { type Instance, components } from "@webstudio-is/react-sdk";
import { shortcuts, options } from "~/shared/shortcuts";
import { publish, useSubscribe } from "~/shared/pubsub";
import { useSelectedInstance } from "./nano-states";
import { copy, paste } from "./copy-paste";
import { useTextEditingInstanceId } from "~/shared/nano-states";
import { type SelectedInstanceData } from "~/shared/canvas-components";
declare module "~/shared/pubsub" {
export interface PubsubMap {
cancelCurrentDrag: undefined;
deleteInstance: {
id: Instance["id"];
};
openBreakpointsMenu: undefined;
selectBreakpointFromShortcut: number;
selectInstance?: SelectedInstanceData;
togglePreviewMode: undefined;
zoom: "zoomOut" | "zoomIn";
}
}
const inputTags = ["INPUT", "SELECT", "TEXTAREA"] as const;
type HandlerEvent = {
key: string;
key?: string;
preventDefault?: () => void;
};
const togglePreviewMode = () => {
publish<"togglePreviewMode">({ type: "togglePreviewMode" });
publish({ type: "togglePreviewMode" });
};
const publishSelectBreakpoint = ({ key }: HandlerEvent) => {
if (!key) {
throw new Error(
"`publishSelectBreakpoint` doesn't account for being called without a `key`"
);
}
publish({
type: "selectBreakpointFromShortcut",
payload: key,
payload: parseInt(key, 10),
});
};
const publishZoom = (event: HandlerEvent) => {
if (!event.key) {
throw new Error(
"`publishZoom` doesn't account for being called without a `key`"
);
}
if (event.preventDefault !== undefined) event.preventDefault();
publish({
type: "zoom",
@@ -53,7 +74,7 @@ export const useShortcuts = () => {
if (selectedInstance === undefined) {
return;
}
publish<"deleteInstance", { id: Instance["id"] }>({
publish({
type: "deleteInstance",
payload: { id: selectedInstance.id },
});
@@ -89,7 +110,7 @@ export const useShortcuts = () => {
return;
}
setSelectedInstance(undefined);
publish<"selectInstance">({ type: "selectInstance" });
publish({ type: "selectInstance" });
},
{ ...options, enableOnContentEditable: true, enableOnTags: [...inputTags] },
[selectedInstance, editingInstanceId]
@@ -133,10 +154,7 @@ export const useShortcuts = () => {
useHotkeys(shortcuts.esc, shortcutHandlerMap.esc, options, []);
// Shortcuts from the parent window
useSubscribe<"shortcut", { name: keyof typeof shortcuts; key: string }>(
"shortcut",
({ name, key }) => {
shortcutHandlerMap[name]({ key });
}
);
useSubscribe("shortcut", ({ name, key }) => {
shortcutHandlerMap[name]({ key });
});
};
@@ -1,10 +1,6 @@
import { useCallback, useEffect, useRef } from "react";
import {
type Instance,
publish,
useSubscribe,
components,
} from "@webstudio-is/react-sdk";
import { type Instance, components } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useSelectedElement, useSelectedInstance } from "./nano-states";
import {
useRootInstance,
@@ -18,6 +14,13 @@ import {
getInstanceElementById,
getInstanceIdFromElement,
} from "~/shared/dom-utils";
import { publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
clickCanvas: undefined;
}
}
const eventOptions = {
passive: true,
@@ -39,10 +42,7 @@ export const useTrackSelectedElement = () => {
[setSelectedInstance, rootInstance]
);
useSubscribe<"selectInstanceById", Instance["id"]>(
"selectInstanceById",
selectInstance
);
useSubscribe("selectInstanceById", selectInstance);
// Focus and select the element when selected instance changes
useEffect(() => {
@@ -70,7 +70,7 @@ export const useTrackSelectedElement = () => {
const handleClick = (event: MouseEvent) => {
// Notify in general that document was clicked
// e.g. to hide the side panel
publish<"clickCanvas">({ type: "clickCanvas" });
publish({ type: "clickCanvas" });
let element = event.target as HTMLElement;
// If we click on an element that is not a component, we search for a parent component.
+4 -12
View File
@@ -5,10 +5,6 @@ import {
type Publish,
} from "@webstudio-is/react-sdk";
import type { Config } from "~/config";
import type {
HoveredInstanceData,
SelectedInstanceData,
} from "~/shared/canvas-components";
import { Box, Flex, Grid, type CSS } from "@webstudio-is/design-system";
import interStyles from "~/shared/font-faces/inter.css";
import { SidebarLeft } from "./features/sidebar-left";
@@ -32,7 +28,6 @@ import {
CanvasIframe,
} from "./features/workspace";
import { usePublishShortcuts } from "./shared/shortcuts";
import { type SyncStatus } from "~/shared/sync";
import {
useIsPreviewMode,
useRootInstance,
@@ -52,25 +47,22 @@ export const links = () => {
const useSubscribeRootInstance = () => {
const [, setValue] = useRootInstance();
useSubscribe<"loadRootInstance">("loadRootInstance", setValue);
useSubscribe("loadRootInstance", setValue);
};
const useSubscribeSelectedInstanceData = () => {
const [, setValue] = useSelectedInstanceData();
useSubscribe<"selectInstance", SelectedInstanceData>(
"selectInstance",
setValue
);
useSubscribe("selectInstance", setValue);
};
const useSubscribeHoveredInstanceData = () => {
const [, setValue] = useHoveredInstanceData();
useSubscribe<"hoverInstance", HoveredInstanceData>("hoverInstance", setValue);
useSubscribe("hoverInstance", setValue);
};
const useSubscribeSyncStatus = () => {
const [, setValue] = useSyncStatus();
useSubscribe<"syncStatus", SyncStatus>("syncStatus", setValue);
useSubscribe("syncStatus", setValue);
};
const useNavigatorLayout = () => {
@@ -6,9 +6,16 @@ import {
darkTheme,
} from "@webstudio-is/design-system";
import { useSelectedInstancePath } from "../../shared/instance/use-selected-instance-path";
import { type Publish, type Instance } from "@webstudio-is/react-sdk";
import { type Instance } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { useSelectedInstanceData } from "~/designer/shared/nano-states";
declare module "~/shared/pubsub" {
export interface PubsubMap {
selectInstanceById: Instance["id"];
}
}
type BreadcrumbProps = {
component: Instance["component"];
onClick: () => void;
@@ -73,7 +80,7 @@ export const Breadcrumbs = ({ publish }: BreadcrumbsProps) => {
key={instance.id}
component={instance.component}
onClick={() => {
publish<"selectInstanceById", Instance["id"]>({
publish({
type: "selectInstanceById",
payload: instance.id,
});
@@ -1,6 +1,7 @@
import { useState } from "react";
import useDebounce from "react-use/lib/useDebounce";
import { type Breakpoint, type Publish } from "@webstudio-is/react-sdk";
import { type Breakpoint } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import {
Button,
TextField,
@@ -1,9 +1,6 @@
import { useEffect, useState } from "react";
import {
useSubscribe,
type Breakpoint,
type Publish,
} from "@webstudio-is/react-sdk";
import { type Breakpoint } from "@webstudio-is/react-sdk";
import { useSubscribe, type Publish } from "~/shared/pubsub";
import {
DropdownMenu,
DropdownMenuContent,
@@ -29,6 +26,14 @@ import {
import { ConfirmationDialog } from "./confirmation-dialog";
import { useBreakpoints } from "~/shared/nano-states";
declare module "~/shared/pubsub" {
export interface PubsubMap {
breakpointDelete: Breakpoint;
breakpointChange: Breakpoint;
loadBreakpoints: Array<Breakpoint>;
}
}
type BreakpointSelectorItemProps = {
breakpoint: Breakpoint;
};
@@ -1,5 +1,5 @@
import { useEffect } from "react";
import { Breakpoint, useSubscribe } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useSelectedBreakpoint } from "~/designer/shared/nano-states";
import { sort } from "~/shared/breakpoints";
import { useBreakpoints } from "~/shared/nano-states";
@@ -8,10 +8,7 @@ export const useSubscribeBreakpoints = () => {
const [breakpoints, setBreakpoints] = useBreakpoints();
const [selectedBreakpoint, setSelectedBreakpoint] = useSelectedBreakpoint();
useSubscribe<"loadBreakpoints", Array<Breakpoint>>(
"loadBreakpoints",
setBreakpoints
);
useSubscribe("loadBreakpoints", setBreakpoints);
useEffect(() => {
// Set the initial selected breakpoint
@@ -1,4 +1,4 @@
import { useSubscribe } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { sort } from "~/shared/breakpoints";
import { useBreakpoints } from "~/shared/nano-states";
import { useZoom, useSelectedBreakpoint } from "../../shared/nano-states";
@@ -7,20 +7,17 @@ import { minZoom } from "./zoom-setting";
export const useSubscribeSelectBreakpointFromShortcut = () => {
const [breakpoints] = useBreakpoints();
const [, setSelectedBreakpoint] = useSelectedBreakpoint();
useSubscribe<"selectBreakpointFromShortcut", number>(
"selectBreakpointFromShortcut",
(index) => {
const breakpoint = sort(breakpoints)[index - 1];
if (breakpoint) setSelectedBreakpoint(breakpoint);
}
);
useSubscribe("selectBreakpointFromShortcut", (index) => {
const breakpoint = sort(breakpoints)[index - 1];
if (breakpoint) setSelectedBreakpoint(breakpoint);
});
};
const zoomStep = 20;
export const useSubscribeZoomFromShortcut = () => {
const [zoom, setZoom] = useZoom();
useSubscribe<"zoom", "zoomIn" | "zoomOut">("zoom", (direction) => {
useSubscribe("zoom", (direction) => {
if (direction === "zoomIn") {
setZoom(Math.min(zoom + zoomStep, 100));
return;
@@ -1,4 +1,4 @@
import { useSubscribe } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useCallback, useEffect } from "react";
import {
useSelectedBreakpoint,
@@ -1,4 +1,4 @@
import type { Publish } from "@webstudio-is/react-sdk";
import type { Publish } from "~/shared/pubsub";
import {
__DEPRECATED__Text,
Tabs,
@@ -1,9 +1,9 @@
import {
componentsMeta,
type Instance,
type Publish,
type UserProp,
} from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { Control } from "./control";
import { CollapsibleSection, ComponentInfo } from "~/designer/shared/inspector";
import type { SelectedInstanceData } from "~/shared/canvas-components";
@@ -1,9 +1,9 @@
import type {
DeleteProp,
Publish,
UserProp,
UserPropsUpdates,
} from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { componentsMeta } from "@webstudio-is/react-sdk";
import ObjectId from "bson-objectid";
import produce from "immer";
@@ -12,6 +12,13 @@ import debounce from "lodash/debounce";
import { useRef, useState } from "react";
import type { SelectedInstanceData } from "~/shared/canvas-components";
declare module "~/shared/pubsub" {
export interface PubsubMap {
deleteProp: DeleteProp;
updateProps: UserPropsUpdates;
}
}
export type handleChangePropType = (
id: UserProp["id"],
field: "prop" | "value",
@@ -87,7 +94,7 @@ export const usePropsLogic = ({
// @todo this may call the last callback after unmount
// @todo we don't need to debounce change events on select for instance, so debounce is only needed on text inputs
const updateProps = debounce((updates: UserPropsUpdates["updates"]) => {
publish<"updateProps", UserPropsUpdates>({
publish({
type: "updateProps",
payload: {
treeId: selectedInstanceData.props.treeId,
@@ -102,7 +109,7 @@ export const usePropsLogic = ({
}, 100);
const deleteProp = (id: UserProp["id"]) => {
publish<"deleteProp", DeleteProp>({
publish({
type: "deleteProp",
payload: {
instanceId: selectedInstanceData.id,
@@ -1,11 +1,23 @@
import { Flex } from "@webstudio-is/design-system";
import { type Instance, type Publish } from "@webstudio-is/react-sdk";
import type { Instance } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { useCallback } from "react";
import { useSelectedInstanceData } from "~/designer/shared/nano-states";
import { useRootInstance } from "~/shared/nano-states";
import { Header } from "../header";
import { InstanceTree } from "~/designer/shared/tree";
declare module "~/shared/pubsub" {
export interface PubsubMap {
selectInstanceById: Instance["id"];
reparentInstance: {
instanceId: Instance["id"];
dropTarget: { instanceId: Instance["id"]; position: number | "end" };
};
deleteInstance: { id: Instance["id"] };
}
}
type NavigatorProps = {
publish: Publish;
isClosable?: boolean;
@@ -18,7 +30,7 @@ export const Navigator = ({ publish, isClosable, onClose }: NavigatorProps) => {
const handleSelect = useCallback(
(instanceId: Instance["id"]) => {
publish<"selectInstanceById", Instance["id"]>({
publish({
type: "selectInstanceById",
payload: instanceId,
});
@@ -26,16 +38,12 @@ export const Navigator = ({ publish, isClosable, onClose }: NavigatorProps) => {
[publish]
);
type ReparentInstancePayload = {
instanceId: Instance["id"];
dropTarget: { instanceId: Instance["id"]; position: number | "end" };
};
const handleDragEnd = useCallback(
(payload: {
itemId: string;
dropTarget: { itemId: string; position: number | "end" };
}) => {
publish<"reparentInstance", ReparentInstancePayload>({
publish({
type: "reparentInstance",
payload: {
instanceId: payload.itemId,
@@ -51,7 +59,7 @@ export const Navigator = ({ publish, isClosable, onClose }: NavigatorProps) => {
const handleDelete = useCallback(
(instanceId: Instance["id"]) => {
publish<"deleteInstance", { id: Instance["id"] }>({
publish({
type: "deleteInstance",
payload: { id: instanceId },
});
@@ -1,11 +1,7 @@
import { type MouseEventHandler, useState } from "react";
import { createPortal } from "react-dom";
import {
type Instance,
type Publish,
components,
useSubscribe,
} from "@webstudio-is/react-sdk";
import { type Instance, components } from "@webstudio-is/react-sdk";
import { useSubscribe, type Publish } from "~/shared/pubsub";
import { Flex } from "@webstudio-is/design-system";
import { useDrag, type Point } from "@webstudio-is/design-system";
import { PlusIcon } from "@webstudio-is/icons";
@@ -13,11 +9,6 @@ import { createInstance } from "~/shared/tree-utils";
import type { TabName } from "../../types";
import { ComponentThumb } from "./component-thumb";
import { useCanvasRect, useZoom } from "~/designer/shared/nano-states";
import {
DragEndPayload,
DragMovePayload,
DragStartPayload,
} from "~/canvas/shared/use-drag-drop";
import { Header } from "../../lib/header";
const componentNames = (
@@ -112,7 +103,7 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => {
},
onStart({ data: componentName }) {
setDragComponent(componentName);
publish<"dragStart", DragStartPayload>({
publish({
type: "dragStart",
payload: {
origin: "panel",
@@ -122,14 +113,14 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => {
},
onMove: (point) => {
setPoint(point);
publish<"dragMove", DragMovePayload>({
publish({
type: "dragMove",
payload: { canvasCoordinates: toCanvasCoordinates(point) },
});
},
onEnd({ isCanceled }) {
setDragComponent(undefined);
publish<"dragEnd", DragEndPayload>({
publish({
type: "dragEnd",
payload: { origin: "panel", isCanceled },
});
@@ -160,7 +151,7 @@ export const TabContent = ({ publish, onSetActiveTab }: TabContentProps) => {
component={component}
onClick={() => {
onSetActiveTab("none");
publish<"insertInstance", { instance: Instance }>({
publish({
type: "insertInstance",
payload: { instance: createInstance({ component }) },
});
@@ -1,4 +1,4 @@
import { type Publish } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { ListNestedIcon } from "@webstudio-is/icons";
import { Navigator } from "../../lib/navigator";
import { TabName } from "../../types";
@@ -1,5 +1,5 @@
import { useState } from "react";
import { useSubscribe, type Publish } from "@webstudio-is/react-sdk";
import { useSubscribe, type Publish } from "~/shared/pubsub";
import type { Asset } from "@webstudio-is/prisma-client";
import {
Box,
@@ -28,10 +28,10 @@ export const SidebarLeft = ({ publish, assets }: SidebarLeftProps) => {
const { TabContent } = activeTab === "none" ? none : panels[activeTab];
const [clientSettings] = useClientSettings();
useSubscribe<"clickCanvas">("clickCanvas", () => {
useSubscribe("clickCanvas", () => {
setActiveTab("none");
});
useSubscribe<"dragEnd">("dragEnd", () => {
useSubscribe("dragEnd", () => {
setActiveTab("none");
});
@@ -1,4 +1,4 @@
import type { Publish } from "@webstudio-is/react-sdk";
import type { Publish } from "~/shared/pubsub";
import { willRender } from "~/designer/shared/breakpoints";
import { Box, Card, Paragraph } from "@webstudio-is/design-system";
import type { SelectedInstanceData } from "~/shared/canvas-components";
@@ -3,13 +3,21 @@ import type {
SelectedInstanceData,
StyleUpdates,
} from "~/shared/canvas-components";
import { type StyleProperty, type Publish } from "@webstudio-is/react-sdk";
import { Instance, type StyleProperty } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { useSelectedBreakpoint } from "../../shared/nano-states";
import { parseCssValue } from "./parse-css-value";
import { getInheritedStyle } from "./get-inherited-style";
import { getCssRuleForBreakpoint } from "./lib/utils/get-css-rule-for-breakpoint";
import { useRootInstance } from "~/shared/nano-states";
declare module "~/shared/pubsub" {
export interface PubsubMap {
updateStyle: StyleUpdates;
[key: `previewStyle:${Instance["id"]}`]: StyleUpdates;
}
}
type UseStyleData = {
publish: Publish;
selectedInstanceData?: SelectedInstanceData;
@@ -73,11 +81,11 @@ export const useStyleData = ({
) {
return;
}
publish<string, StyleUpdates>({
publish({
type:
type === "update"
? "updateStyle"
: `previewStyle:${selectedInstanceData.id}`,
: (`previewStyle:${selectedInstanceData.id}` as const),
payload: {
id: selectedInstanceData.id,
updates,
@@ -1,5 +1,5 @@
import { useNavigate } from "react-router-dom";
import { type Publish } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import {
DropdownMenu,
DropdownMenuContent,
@@ -140,9 +140,9 @@ export const Menu = ({ config, publish }: MenuProps) => {
<DropdownMenuItem
css={menuItemCss}
onSelect={() => {
publish<"shortcut", string>({
publish({
type: "shortcut",
payload: "undo",
payload: { name: "undo" },
});
}}
>
@@ -152,9 +152,9 @@ export const Menu = ({ config, publish }: MenuProps) => {
<DropdownMenuItem
css={menuItemCss}
onSelect={() => {
publish<"shortcut", string>({
publish({
type: "shortcut",
payload: "redo",
payload: { name: "redo" },
});
}}
>
@@ -165,9 +165,9 @@ export const Menu = ({ config, publish }: MenuProps) => {
<DropdownMenuItem
css={menuItemCss}
onSelect={() => {
publish<"shortcut", string>({
publish({
type: "shortcut",
payload: "copy",
payload: { name: "copy" },
});
}}
>
@@ -177,9 +177,9 @@ export const Menu = ({ config, publish }: MenuProps) => {
<DropdownMenuItem
css={menuItemCss}
onSelect={() => {
publish<"shortcut", string>({
publish({
type: "shortcut",
payload: "paste",
payload: { name: "paste" },
});
}}
>
@@ -189,9 +189,9 @@ export const Menu = ({ config, publish }: MenuProps) => {
<DropdownMenuItem
css={menuItemCss}
onSelect={() => {
publish<"shortcut", string>({
publish({
type: "shortcut",
payload: "delete",
payload: { name: "delete" },
});
}}
>
@@ -238,7 +238,7 @@ export const Menu = ({ config, publish }: MenuProps) => {
<DropdownMenuItem
css={menuItemCss}
onSelect={() => {
publish<"togglePreviewMode">({
publish({
type: "togglePreviewMode",
});
}}
@@ -1,8 +1,14 @@
import { useSubscribe, type Publish } from "@webstudio-is/react-sdk";
import { useSubscribe, type Publish } from "~/shared/pubsub";
import { EyeOpenIcon } from "@webstudio-is/icons";
import { SimpleToggle } from "@webstudio-is/design-system";
import { useIsPreviewMode } from "~/shared/nano-states";
declare module "~/shared/pubsub" {
export interface PubsubMap {
previewMode: boolean;
}
}
type PreviewButtonProps = {
publish: Publish;
};
@@ -12,13 +18,13 @@ export const PreviewButton = ({ publish }: PreviewButtonProps) => {
const setValue = (value: boolean) => {
setIsPreviewMode(value);
publish<"previewMode", boolean>({
publish({
type: "previewMode",
payload: value,
});
};
useSubscribe<"togglePreviewMode">("togglePreviewMode", () => {
useSubscribe("togglePreviewMode", () => {
setValue(isPreviewMode === false);
});
@@ -1,4 +1,4 @@
import { type Publish } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { Box } from "@webstudio-is/design-system";
import { PlacementIndicator } from "@webstudio-is/design-system";
import {
@@ -1,10 +1,7 @@
import { type Instance, useSubscribe } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useTextEditingInstanceId } from "~/shared/nano-states";
export const useSubscribeTextEditingInstanceId = () => {
const [, setInstanceId] = useTextEditingInstanceId();
useSubscribe<"textEditingInstanceId", Instance["id"] | undefined>(
"textEditingInstanceId",
setInstanceId
);
useSubscribe("textEditingInstanceId", setInstanceId);
};
@@ -1,4 +1,4 @@
import { useSubscribe } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import {
useSelectedInstanceRect,
useHoveredInstanceRect,
@@ -6,13 +6,7 @@ import {
export const useSubscribeInstanceRect = () => {
const [, setSelectedRect] = useSelectedInstanceRect();
useSubscribe<"selectedInstanceRect", DOMRect>(
"selectedInstanceRect",
setSelectedRect
);
useSubscribe("selectedInstanceRect", setSelectedRect);
const [, setHoveredRect] = useHoveredInstanceRect();
useSubscribe<"hoveredInstanceRect", DOMRect>(
"hoveredInstanceRect",
setHoveredRect
);
useSubscribe("hoveredInstanceRect", setHoveredRect);
};
@@ -1,7 +1,7 @@
import { useSubscribe } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useSelectionRect } from "~/designer/shared/nano-states";
export const useSubscribeSelectionRect = () => {
const [, setSelectionRect] = useSelectionRect();
useSubscribe<"selectionRect", DOMRect>("selectionRect", setSelectionRect);
useSubscribe("selectionRect", setSelectionRect);
};
@@ -1,4 +1,4 @@
import { type Instance, type Publish } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { useMemo, useState, type MouseEventHandler } from "react";
import {
useSelectedInstanceData,
@@ -105,7 +105,7 @@ export const TextToolbar = ({ publish }: TextToolbarProps) => {
css={placement}
onValueChange={(component) => {
const instance = createInstance({ component });
publish<"insertInlineInstance", Instance>({
publish({
type: "insertInlineInstance",
payload: instance,
});
@@ -1,7 +1,19 @@
import { Box, Flex, Toaster } from "@webstudio-is/design-system";
import { type Instance, type InstanceProps } from "@webstudio-is/react-sdk";
import { useCanvasWidth, useZoom } from "~/designer/shared/nano-states";
import { CanvasTools } from "./canvas-tools";
import { type Publish } from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
insertInstance: {
instance: Instance;
dropTarget?: { parentId: Instance["id"]; position: number };
props?: InstanceProps;
};
unselectInstance: undefined;
}
}
const workspaceStyle = {
flexGrow: 1,
@@ -39,7 +51,7 @@ export const Workspace = ({
const [canvasWidth] = useCanvasWidth();
const handleWorkspaceClick = () => {
publish<"unselectInstance">({ type: "unselectInstance" });
publish({ type: "unselectInstance" });
};
return (
@@ -1,6 +1,12 @@
import { useHotkeys } from "react-hotkeys-hook";
import { type Publish } from "@webstudio-is/react-sdk";
import { shortcuts, options } from "~/shared/shortcuts";
import { type Publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
shortcut: { name: keyof typeof shortcuts; key?: string };
}
}
const names = Object.keys(shortcuts) as Array<keyof typeof shortcuts>;
@@ -15,7 +21,7 @@ export const usePublishShortcuts = (publish: Publish) => {
shortcuts[name],
(event) => {
event.preventDefault();
publish<"shortcut", { name: string; key: string }>({
publish({
type: "shortcut",
payload: { name, key: event.key },
});
@@ -1,5 +1,11 @@
import { publish } from "@webstudio-is/react-sdk";
import { useEffect } from "react";
import { publish } from "~/shared/pubsub";
declare module "~/shared/pubsub" {
export interface PubsubMap {
canvasWidth: number;
}
}
export const ErrorMessage = ({ message }: { message: string }) => {
useEffect(() => {
@@ -1,31 +1,20 @@
import { useSubscribe } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useDragAndDropState } from "./nano-states";
import {
DropTargetChangePayload,
DragStartPayload,
DragEndPayload,
} from "~/canvas/shared/use-drag-drop";
export const useSubscribeDragAndDropState = () => {
const [state, setState] = useDragAndDropState();
useSubscribe<"dragStart", DragStartPayload>(
"dragStart",
({ origin, dragItem }) => {
// It's possible that dropTargetChange comes before dragStart.
// So it's important to spread the current ...state here.
setState({ ...state, isDragging: true, origin, dragItem });
}
);
useSubscribe("dragStart", ({ origin, dragItem }) => {
// It's possible that dropTargetChange comes before dragStart.
// So it's important to spread the current ...state here.
setState({ ...state, isDragging: true, origin, dragItem });
});
useSubscribe<"dropTargetChange", DropTargetChangePayload>(
"dropTargetChange",
(dropTarget) => {
setState({ ...state, dropTarget });
}
);
useSubscribe("dropTargetChange", (dropTarget) => {
setState({ ...state, dropTarget });
});
useSubscribe<"dragEnd", DragEndPayload>("dragEnd", () => {
useSubscribe("dragEnd", () => {
setState({ isDragging: false });
});
};
@@ -1,7 +1,7 @@
import { useSubscribe } from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { useIsScrolling } from "./nano-states";
export const useSubscribeScrollState = () => {
const [, setIsScrolling] = useIsScrolling();
useSubscribe<"scrollState", boolean>("scrollState", setIsScrolling);
useSubscribe("scrollState", setIsScrolling);
};
+9
View File
@@ -0,0 +1,9 @@
import { createPubsub } from "@webstudio-is/react-sdk";
// this interface is meant to be augmented by the user
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PubsubMap {}
export const { publish, usePublish, useSubscribe } = createPubsub<PubsubMap>();
export type Publish = typeof publish;
export type UsePublish = typeof usePublish;
@@ -5,6 +5,7 @@ export const shortcuts = {
preview: "cmd+shift+p, ctrl+shift+p",
copy: "cmd+c, ctrl+c",
paste: "cmd+v, ctrl+v",
delete: "backspace, delete",
breakpointsMenu: "cmd+b, ctrl+b",
breakpoint: Array.from(new Array(9))
.map((_, index) => `cmd+${index + 1}, ctrl+${index + 1}`)
+2 -1
View File
@@ -31,5 +31,6 @@
"packageManager": "yarn@1.22.19",
"lint-staged": {
"*.{ts,tsx,js,json,css,md}": "prettier --write"
}
},
"prettier": {}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@webstudio-is/asset-uploader",
"version": "0.0.1",
"version": "0.1.0",
"description": "Webstudio Asset Uploader",
"author": "Webstudio <github@webstudio.is>",
"homepage": "https://webstudio.is",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@webstudio-is/design-system",
"version": "0.1.0",
"version": "0.1.1",
"description": "Webstudio Design System",
"author": "Webstudio <github@webstudio.is>",
"homepage": "https://webstudio.is",
@@ -14,12 +14,12 @@ export const Text = styled("div", {
variant: {
regular: {
fontWeight: 400,
fontSize: 12,
fontSize: "$1",
letterSpacing: "0.005em",
},
label: {
fontWeight: 500,
fontSize: 12,
fontSize: "$1",
letterSpacing: "0.005em",
},
tiny: {
@@ -29,19 +29,18 @@ export const Text = styled("div", {
},
title: {
fontWeight: 700,
fontSize: 12,
fontSize: "$1",
letterSpacing: "0.01em",
},
mono: {
fontFamily: "$mono",
fontWeight: 400,
fontSize: 12,
fontSize: "$1",
textTransform: "uppercase",
},
unit: {
fontWeight: 500,
fontSize: 10,
lineHeight: 12,
textTransform: "uppercase",
},
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@webstudio-is/eslint-config-custom",
"version": "1.0.0",
"version": "1.0.1",
"main": "index.js",
"license": "MIT",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@webstudio-is/icons",
"version": "0.1.0",
"version": "0.1.1",
"description": "Webstudio Icons",
"author": "Webstudio <github@webstudio.is>",
"homepage": "https://webstudio.is",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@webstudio-is/jest-config",
"version": "1.0.1",
"version": "1.0.2",
"license": "MIT",
"dependencies": {
"@types/jest": "^27.4.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@webstudio-is/prisma-client",
"version": "0.0.1",
"version": "0.1.0",
"description": "Webstudio Prisma layer",
"author": "Webstudio <github@webstudio.is>",
"homepage": "https://webstudio.is",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@webstudio-is/react-sdk",
"version": "0.5.0",
"version": "0.5.1",
"description": "Webstudio JavaScript / TypeScript API",
"author": "Webstudio <github@webstudio.is>",
"homepage": "https://webstudio.is",
+70
View File
@@ -0,0 +1,70 @@
import mitt from "mitt";
import { useCallback, useEffect, useRef } from "react";
export const createPubsub = <PublishMap>() => {
type Action<Type extends keyof PublishMap> =
undefined extends PublishMap[Type]
? { type: Type; payload?: PublishMap[Type] }
: { type: Type; payload: PublishMap[Type] };
// `mitt` has a somewhat annoying overload for `*` type that makes it hard to wrap in a generic context
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const emitter = mitt<Record<any, any>>();
if (typeof window === "object") {
window.addEventListener(
"message",
(event: MessageEvent) => {
// @todo this has no type safety built in, could be anything from any source.
// we could potentially maintain a list of valid event types at runtime
// at the very least we could add a brand property or something to our events
if (typeof event.data?.type === "string") {
emitter.emit(event.data.type, event.data.payload);
}
},
false
);
}
return {
/**
* To publish a postMessage event on the current window and parent window from the iframe.
*/
publish<Type extends keyof PublishMap>(action: Action<Type>) {
window.parent.postMessage(action, "*");
window.postMessage(action, "*");
},
/**
* To publish a postMessage event on the iframe and parent window from the parent window.
*/
usePublish() {
const iframeRef = useRef<HTMLIFrameElement | null>(null);
const publishCallback = useCallback(
<Type extends keyof PublishMap>(action: Action<Type>) => {
const element = iframeRef.current;
if (element?.contentWindow == null) return;
element.contentWindow.postMessage(action, "*");
window.postMessage(action, "*");
},
[iframeRef]
);
return [publishCallback, iframeRef] as const;
},
/**
* To subscribe a message event on the current window.
*/
useSubscribe<Type extends keyof PublishMap>(
type: Type,
onAction: (payload: PublishMap[Type]) => void
) {
useEffect(() => {
emitter.on(type, onAction);
return () => {
emitter.off(type, onAction);
};
}, [type, onAction]);
},
};
};
+1 -2
View File
@@ -1,2 +1 @@
export * from "./publish";
export * from "./subscribe";
export * from "./create";
-39
View File
@@ -1,39 +0,0 @@
import { useRef, useCallback, MutableRefObject } from "react";
type Action<Type, Payload> = {
type: Type;
payload?: Payload;
};
export type Publish = <Type, Payload = undefined>(
action: Action<Type, Payload>
) => void;
type UsePublish = [Publish, MutableRefObject<HTMLIFrameElement | null>];
/**
* To publish a postMessage event on the iframe and parent window from the parent window.
*/
export const usePublish = (): UsePublish => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const publishCallback: Publish = useCallback(
(action) => {
const element = iframeRef.current;
if (element?.contentWindow == null) return;
element.contentWindow.postMessage(action, "*");
window.postMessage(action, "*");
},
[iframeRef]
);
return [publishCallback, iframeRef];
};
/**
* To publish a postMessage event on the current window and parent window from the iframe.
*/
export const publish = <Type extends string, Payload = undefined>(
action: Action<Type, Payload>
) => {
window.parent.postMessage(action, "*");
window.postMessage(action, "*");
};
@@ -1,40 +0,0 @@
import mitt from "mitt";
import { useEffect } from "react";
// @todo do we want to have a central map of all events/data?
// Pro - you can't use a wrong event with a wrong data
// Contra - if we have hundreds of events, this list will become a central thing to change,
// may result in maintenance downsides and merge conflicts, because it will have to import each data type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Events = Record<string, any>;
export const emitter = mitt<Events>();
if (typeof window === "object") {
window.addEventListener(
"message",
(event: MessageEvent) => {
// @todo this has no type safety built in, could be anything from any source.
// we could potentially maintain a list of valid event types, but see prior contra points.
if (typeof event.data?.type === "string") {
emitter.emit(event.data.type, event.data.payload);
}
},
false
);
}
/**
* To subscribe a message event on the current window.
*/
export const useSubscribe = <Type extends string, Payload = undefined>(
type: Type,
onAction: (payload: Payload) => void
) => {
useEffect(() => {
emitter.on(type, onAction);
return () => {
emitter.off(type, onAction);
};
}, [type, onAction]);
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@webstudio-is/tsconfig",
"version": "1.0.0",
"version": "1.0.1",
"private": false,
"main": "index.js",
"files": [