Skip to content
Open
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
6 changes: 5 additions & 1 deletion apps/builder/app/builder/features/address-bar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ const HistoryInspect = () => {
const page = useStore($selectedPage);
return (
<Text variant="mono" css={{ whiteSpace: "pre" }}>
{JSON.stringify(page?.history, null, 2)}
{JSON.stringify(
page !== undefined && "history" in page ? page.history : undefined,
null,
2
)}
</Text>
);
};
Expand Down
6 changes: 3 additions & 3 deletions apps/builder/app/builder/features/address-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ import {
matchPathnamePattern,
tokenizePathnamePattern,
} from "~/builder/shared/url-pattern";
import { isPage } from "@webstudio-is/sdk";
import { $selectedPage, $selectedPagePath } from "~/shared/nano-states";
import { $currentSystem, updateCurrentSystem } from "~/shared/system";

const $selectedPageHistory = computed(
$selectedPage,
(page) => page?.history ?? []
const $selectedPageHistory = computed($selectedPage, (page): string[] =>
page !== undefined && isPage(page) ? (page.history ?? []) : []
);

const useCopyUrl = (pageUrl: string) => {
Expand Down
52 changes: 51 additions & 1 deletion apps/builder/app/builder/features/pages/confirmation-dialogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Button,
theme,
} from "@webstudio-is/design-system";
import type { Page, Folder } from "@webstudio-is/sdk";
import type { Page, Folder, PageTemplate } from "@webstudio-is/sdk";

type DeletePageConfirmationDialogProps = {
onClose: () => void;
Expand Down Expand Up @@ -106,3 +106,53 @@ export const DeleteFolderConfirmationDialog = ({
</Dialog>
);
};

type DeleteTemplateConfirmationDialogProps = {
onClose: () => void;
onConfirm: () => void;
template: PageTemplate;
};

export const DeleteTemplateConfirmationDialog = ({
onClose,
onConfirm,
template,
}: DeleteTemplateConfirmationDialogProps) => {
return (
<Dialog
open
onOpenChange={(isOpen) => {
if (isOpen === false) {
onClose();
}
}}
>
<DialogContent>
<DialogTitle>Delete template</DialogTitle>
<Flex gap="3" direction="column" css={{ padding: theme.panel.padding }}>
<Text>{`Are you sure you want to delete the template "${template.name}"?`}</Text>
<Text>
You can undo it even if you delete the template as long as you don't
reload.
</Text>
</Flex>
<DialogActions>
<DialogClose>
<Button
autoFocus
color="destructive"
onClick={() => {
onConfirm();
}}
>
Delete Template
</Button>
</DialogClose>
<DialogClose>
<Button color="ghost">Cancel</Button>
</DialogClose>
</DialogActions>
</DialogContent>
</Dialog>
);
};
39 changes: 26 additions & 13 deletions apps/builder/app/builder/features/pages/custom-metadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ type Meta = {
type CustomMetadataProps = {
customMetas: Meta[];
onChange: (value: Meta[]) => void;
disabled?: boolean;
showBindingControls?: boolean;
};

const MetadataItem = (props: {
property: string;
content: string;
disabled?: boolean;
showBindingControls?: boolean;
onDelete: () => void;
onChange: (property: string, content: string) => void;
}) => {
Expand Down Expand Up @@ -61,6 +65,7 @@ const MetadataItem = (props: {
css={{ gridArea: "property-input" }}
id={propertyId}
property="path"
disabled={props.disabled}
value={props.property}
onChange={(event) => {
props.onChange(event.target.value, props.content);
Expand All @@ -71,24 +76,28 @@ const MetadataItem = (props: {
Content
</Label>
<BindingControl>
<BindingPopover
scope={scope}
aliases={aliases}
variant={isLiteralExpression(props.content) ? "default" : "bound"}
value={props.content}
onChange={(value) => {
props.onChange(props.property, value);
}}
onRemove={(evaluatedValue) => {
props.onChange(props.property, JSON.stringify(evaluatedValue));
}}
/>
{props.showBindingControls && (
<BindingPopover
scope={scope}
aliases={aliases}
variant={isLiteralExpression(props.content) ? "default" : "bound"}
value={props.content}
onChange={(value) => {
props.onChange(props.property, value);
}}
onRemove={(evaluatedValue) => {
props.onChange(props.property, JSON.stringify(evaluatedValue));
}}
/>
)}
<InputErrorsTooltip errors={undefined}>
<InputField
css={{
gridArea: "content-input",
}}
disabled={isLiteralExpression(props.content) === false}
disabled={
props.disabled || isLiteralExpression(props.content) === false
}
color={typeof content !== "string" ? "error" : undefined}
id={contentId}
property="path"
Expand Down Expand Up @@ -123,6 +132,7 @@ const MetadataItem = (props: {
<SmallIconButton
variant="destructive"
icon={<TrashIcon />}
disabled={props.disabled}
onClick={props.onDelete}
/>

Expand Down Expand Up @@ -167,6 +177,8 @@ export const CustomMetadata = (props: CustomMetadataProps) => {
key={index}
property={meta.property}
content={meta.content}
disabled={props.disabled}
showBindingControls={props.showBindingControls}
onChange={(property, content) => {
const newCustomMetas = [...props.customMetas];
newCustomMetas[index] = { property, content };
Expand All @@ -187,6 +199,7 @@ export const CustomMetadata = (props: CustomMetadataProps) => {
justifySelf: "center",
}}
prefix={<PlusIcon />}
disabled={props.disabled}
onClick={() => {
const newCustomMetas = [
...props.customMetas,
Expand Down
9 changes: 7 additions & 2 deletions apps/builder/app/builder/features/pages/image-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ import { getFormattedAspectRatio } from "~/builder/shared/asset-manager";
type ImageInfoProps = {
asset: ImageAsset;
onDelete: () => void;
disabled?: boolean;
};

export const ImageInfo = ({ asset, onDelete }: ImageInfoProps) => {
export const ImageInfo = ({
asset,
onDelete,
disabled = false,
}: ImageInfoProps) => {
return (
<Grid gap={1} flow={"column"} align={"center"} justify={"between"}>
<Grid
Expand Down Expand Up @@ -69,7 +74,7 @@ export const ImageInfo = ({ asset, onDelete }: ImageInfoProps) => {
</Grid>
</Grid>
</Grid>
<IconButton onClick={onDelete}>
<IconButton onClick={onDelete} disabled={disabled}>
<TrashIcon />
</IconButton>
</Grid>
Expand Down
73 changes: 72 additions & 1 deletion apps/builder/app/builder/features/pages/page-context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
ContextMenuItem,
ContextMenuTrigger,
} from "@webstudio-is/design-system";
import { duplicatePage, duplicateFolder } from "./page-utils";
import {
duplicatePage,
duplicateFolder,
duplicateTemplate,
} from "./page-utils";
import { selectPage } from "~/shared/nano-states";

type PageContextMenuProps = {
children: ReactNode;
Expand Down Expand Up @@ -85,3 +90,69 @@ export const PageContextMenu = ({
</>
);
};

type TemplateContextMenuProps = {
children: ReactNode;
onRequestDeleteTemplate: (templateId: string) => void;
};

export const TemplateContextMenu = ({
children,
onRequestDeleteTemplate,
}: TemplateContextMenuProps) => {
const [templateId, setTemplateId] = useState<string | undefined>();

const handleDuplicate = () => {
if (templateId) {
const newTemplateId = duplicateTemplate(templateId);
if (newTemplateId) {
selectPage(newTemplateId);
}
}
};

return (
<>
<ContextMenu
onOpenChange={(open) => {
if (!open) {
setTemplateId(undefined);
}
}}
>
<ContextMenuTrigger
asChild
onPointerDown={(event) => {
if (!(event.target instanceof HTMLElement)) {
return;
}
const button =
event.target.closest<HTMLElement>("[data-tree-button]");
const templateId = button?.getAttribute("data-template-id");
if (templateId) {
setTemplateId(templateId);
}
}}
>
{children}
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem onSelect={handleDuplicate} disabled={!templateId}>
Duplicate
</ContextMenuItem>
<ContextMenuItem
onSelect={() => {
if (templateId) {
onRequestDeleteTemplate(templateId);
}
}}
destructive
disabled={!templateId}
>
Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</>
);
};
Loading
Loading