@peteai/presentation-editor 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/editor/active-layers.svelte +2 -2
- package/dist/components/editor/design.svelte +92 -0
- package/dist/components/editor/design.svelte.d.ts +8 -0
- package/dist/components/editor/editor.svelte +1 -1
- package/dist/components/editor/editor.svelte.js +14 -2
- package/dist/components/editor/header.svelte +1 -1
- package/dist/components/editor/index.d.ts +2 -1
- package/dist/components/editor/index.js +2 -1
- package/dist/components/editor/layers/active-layer-border.svelte +1 -1
- package/dist/components/editor/layers/buttons/border-button/border-button-colors.svelte +1 -1
- package/dist/components/editor/layers/controls/corner-scale-control/corner-scale-control.svelte +1 -1
- package/dist/components/editor/layers/controls/rotate-control/rotate-control.svelte +1 -1
- package/dist/components/editor/layers/controls/side-scale-control/side-scale-control.svelte +1 -1
- package/dist/components/editor/layers/layer-button.svelte +1 -1
- package/dist/components/editor/layers/types/background/background-layer-buttons.svelte +1 -1
- package/dist/components/editor/layers/types/image/controls/image-rotate-control/image-rotate-control.svelte +1 -1
- package/dist/components/editor/layers/types/image/controls/image-scale-control/image-scale-control.svelte +1 -1
- package/dist/components/editor/layers/types/image/image-layer-crop.svelte +2 -2
- package/dist/components/editor/page-editor.svelte +7 -0
- package/dist/components/editor/{pages-navigation/page-preview.svelte → page-preview.svelte} +4 -4
- package/dist/components/editor/{pages-navigation/page-preview.svelte.d.ts → page-preview.svelte.d.ts} +2 -2
- package/dist/components/editor/page-transition-preview.svelte +34 -0
- package/dist/components/editor/page-transition-preview.svelte.d.ts +9 -0
- package/dist/components/editor/page-with-transition.svelte +48 -0
- package/dist/components/editor/page-with-transition.svelte.d.ts +10 -0
- package/dist/components/editor/page.svelte +12 -19
- package/dist/components/editor/pages-navigation/pages-navigation-item.svelte +80 -20
- package/dist/components/editor/pages-navigation/pages-navigation.svelte +7 -4
- package/dist/components/editor/sidebar/color-sidebar/color-sidebar-color.svelte +2 -2
- package/dist/components/editor/sidebar/color-sidebar/color-sidebar-gradient-picker.svelte +5 -5
- package/dist/components/editor/sidebar/color-sidebar/color-sidebar.svelte +91 -81
- package/dist/components/editor/sidebar/color-sidebar/color-sidebar.svelte.d.ts +5 -1
- package/dist/components/editor/sidebar/font-sidebar/font-sidebar.svelte +10 -6
- package/dist/components/editor/sidebar/font-sidebar/font-sidebar.svelte.d.ts +5 -1
- package/dist/components/editor/sidebar/image-crop-sidebar.svelte +28 -37
- package/dist/components/editor/sidebar/image-crop-sidebar.svelte.d.ts +2 -0
- package/dist/components/editor/sidebar/position-sidebar/index.d.ts +2 -0
- package/dist/components/editor/sidebar/position-sidebar/index.js +2 -0
- package/dist/components/editor/sidebar/position-sidebar/position-sidebar.svelte +130 -0
- package/dist/components/editor/sidebar/position-sidebar/position-sidebar.svelte.d.ts +7 -0
- package/dist/components/editor/sidebar/sidebar-text-tab.svelte +7 -3
- package/dist/components/editor/sidebar/sidebar-text-tab.svelte.d.ts +5 -16
- package/dist/components/editor/sidebar/sidebar-uploads-tab.svelte +10 -8
- package/dist/components/editor/sidebar/sidebar-uploads-tab.svelte.d.ts +5 -1
- package/dist/components/editor/sidebar/sidebar-wrapper.svelte +4 -3
- package/dist/components/editor/sidebar/sidebar-wrapper.svelte.d.ts +1 -0
- package/dist/components/editor/sidebar/sidebar.svelte +20 -13
- package/dist/components/editor/sidebar/transition-sidebar/index.d.ts +2 -0
- package/dist/components/editor/sidebar/transition-sidebar/index.js +2 -0
- package/dist/components/editor/sidebar/transition-sidebar/transition-sidebar.svelte +257 -0
- package/dist/components/editor/sidebar/transition-sidebar/transition-sidebar.svelte.d.ts +7 -0
- package/dist/components/editor/snapping-guides.svelte +3 -3
- package/dist/components/editor/types.d.ts +40 -6
- package/dist/components/editor/utils.d.ts +2 -2
- package/dist/components/editor/utils.js +21 -0
- package/dist/components/ui/button/button.svelte +2 -1
- package/dist/components/ui/button/button.svelte.d.ts +3 -0
- package/dist/components/ui/color-picker/color-picker-alpha-grid.svelte +2 -1
- package/dist/components/ui/color-picker/color-picker.svelte +6 -6
- package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-content.svelte +3 -1
- package/dist/components/ui/context-menu/context-menu-group-heading.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-item.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-radio-item.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-separator.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-shortcut.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-sub-content.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte +1 -1
- package/dist/components/ui/dialog/dialog-content.svelte +4 -2
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/dist/components/ui/input/input.svelte +1 -1
- package/dist/components/ui/slider/slider.svelte +3 -3
- package/dist/components/ui/tabs/index.d.ts +4 -4
- package/dist/components/ui/tabs/index.js +4 -4
- package/dist/components/ui/tabs/tabs-content.svelte +4 -4
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +1 -1
- package/dist/components/ui/tabs/tabs-list.svelte +5 -9
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +1 -1
- package/dist/components/ui/tabs/tabs-trigger.svelte +4 -4
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +1 -1
- package/dist/transitions/circleWipe.d.ts +10 -0
- package/dist/transitions/circleWipe.js +16 -0
- package/dist/transitions/lineWipe.d.ts +9 -0
- package/dist/transitions/lineWipe.js +17 -0
- package/dist/transitions/stack.d.ts +8 -0
- package/dist/transitions/stack.js +13 -0
- package/package.json +19 -19
- package/dist/components/editor/layers/types/image/image-layer-active.svelte +0 -36
- package/dist/components/editor/layers/types/image/image-layer-active.svelte.d.ts +0 -7
- package/dist/components/editor/sidebar/position-sidebar.svelte +0 -136
- package/dist/components/editor/sidebar/position-sidebar.svelte.d.ts +0 -3
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { flip } from 'svelte/animate';
|
|
3
|
+
import { dndzone, type DndEvent } from 'svelte-dnd-action';
|
|
4
|
+
import * as ContextMenu from '../../../ui/context-menu/index.js';
|
|
5
|
+
import {
|
|
6
|
+
LayerButton,
|
|
7
|
+
LayerThumbWrapper,
|
|
8
|
+
TextLayerContent,
|
|
9
|
+
ImageLayerContent,
|
|
10
|
+
BackgroundLayerContent,
|
|
11
|
+
GroupLayerContent,
|
|
12
|
+
} from '../../layers/index.js';
|
|
13
|
+
import type { Editor } from '../../editor.svelte.js';
|
|
14
|
+
import LayerMenuContent from '../../menu/layer-menu-content.svelte';
|
|
15
|
+
import BackgroundMenuContent from '../../menu/background-menu-content.svelte';
|
|
16
|
+
import type { Layer } from '../../types.js';
|
|
17
|
+
import SidebarWrapper from '../sidebar-wrapper.svelte';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
editor: Editor;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { editor }: Props = $props();
|
|
24
|
+
|
|
25
|
+
const flipDurationMs = 300;
|
|
26
|
+
const otherOptions = { flipDurationMs, type: 'layers', dropTargetStyle: {} };
|
|
27
|
+
|
|
28
|
+
function handleDndConsider(e: CustomEvent<DndEvent<Layer>>) {
|
|
29
|
+
editor.rootLayers = e.detail.items;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function handleDndFinalize(e: CustomEvent<DndEvent<Layer>>) {
|
|
33
|
+
console.log('handleDndFinalize', e);
|
|
34
|
+
|
|
35
|
+
editor.rootLayers = e.detail.items;
|
|
36
|
+
|
|
37
|
+
const index = e.detail.items.findIndex((item) => item.id === e.detail.info.id);
|
|
38
|
+
const item = e.detail.items[index];
|
|
39
|
+
|
|
40
|
+
let redo;
|
|
41
|
+
const prev = e.detail.items[index - 1];
|
|
42
|
+
const next = e.detail.items[index + 1];
|
|
43
|
+
if (prev && next) {
|
|
44
|
+
if (!(prev.sortOrder < item.sortOrder && next.sortOrder > item.sortOrder)) {
|
|
45
|
+
redo = { sortOrder: (prev.sortOrder + next.sortOrder) / 2 };
|
|
46
|
+
}
|
|
47
|
+
} else if (next) {
|
|
48
|
+
if (next && next.sortOrder <= item.sortOrder) {
|
|
49
|
+
redo = { sortOrder: next.sortOrder - 1 };
|
|
50
|
+
}
|
|
51
|
+
} else if (prev) {
|
|
52
|
+
if (prev && prev.sortOrder >= item.sortOrder) {
|
|
53
|
+
redo = { sortOrder: prev.sortOrder + 1 };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (redo) {
|
|
58
|
+
editor.historyPush({
|
|
59
|
+
type: 'layerUpdate',
|
|
60
|
+
pageId: editor.activePage.id,
|
|
61
|
+
layer: { id: item.id, type: item.type },
|
|
62
|
+
undo: { sortOrder: item.sortOrder },
|
|
63
|
+
redo,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<SidebarWrapper name="Position">
|
|
70
|
+
<div class="flex flex-col gap-2 p-2">
|
|
71
|
+
<section
|
|
72
|
+
class="flex flex-col-reverse gap-2"
|
|
73
|
+
aria-label="Layers"
|
|
74
|
+
use:dndzone={{
|
|
75
|
+
items: editor.rootLayers,
|
|
76
|
+
dragDisabled: editor.activePage.locked,
|
|
77
|
+
...otherOptions,
|
|
78
|
+
}}
|
|
79
|
+
onconsider={handleDndConsider}
|
|
80
|
+
onfinalize={handleDndFinalize}
|
|
81
|
+
>
|
|
82
|
+
{#each editor.rootLayers as layer (layer.id)}
|
|
83
|
+
<div aria-label={layer.type} animate:flip={{ duration: flipDurationMs }}>
|
|
84
|
+
<ContextMenu.Root
|
|
85
|
+
onOpenChange={(open) =>
|
|
86
|
+
open &&
|
|
87
|
+
!editor.activeLayers.find((l) => l.id === layer.id) &&
|
|
88
|
+
editor.setActiveLayers(layer.id)}
|
|
89
|
+
>
|
|
90
|
+
<ContextMenu.Trigger>
|
|
91
|
+
<LayerButton {layer}>
|
|
92
|
+
<LayerThumbWrapper {layer} thumb={{ width: 192, height: 32 }}>
|
|
93
|
+
{#snippet children({ thumbScale })}
|
|
94
|
+
{#if layer.type === 'text'}
|
|
95
|
+
<TextLayerContent {layer} {thumbScale} />
|
|
96
|
+
{:else if layer.type === 'image'}
|
|
97
|
+
<ImageLayerContent {layer} {thumbScale} />
|
|
98
|
+
{:else if layer.type === 'group'}
|
|
99
|
+
<GroupLayerContent page={editor.activePage} {layer} {thumbScale} />
|
|
100
|
+
{/if}
|
|
101
|
+
{/snippet}
|
|
102
|
+
</LayerThumbWrapper>
|
|
103
|
+
</LayerButton>
|
|
104
|
+
</ContextMenu.Trigger>
|
|
105
|
+
<ContextMenu.Content class="w-72">
|
|
106
|
+
<LayerMenuContent />
|
|
107
|
+
</ContextMenu.Content>
|
|
108
|
+
</ContextMenu.Root>
|
|
109
|
+
</div>
|
|
110
|
+
{/each}
|
|
111
|
+
</section>
|
|
112
|
+
<ContextMenu.Root>
|
|
113
|
+
<ContextMenu.Trigger>
|
|
114
|
+
<LayerButton>
|
|
115
|
+
{@const thumbScale = Math.max(192 / editor.width, 32 / editor.height)}
|
|
116
|
+
<div
|
|
117
|
+
class="absolute top-1/2 -translate-y-1/2"
|
|
118
|
+
style:width="{editor.width * thumbScale}px"
|
|
119
|
+
style:height="{editor.height * thumbScale}px"
|
|
120
|
+
>
|
|
121
|
+
<BackgroundLayerContent page={editor.activePage} {thumbScale} />
|
|
122
|
+
</div>
|
|
123
|
+
</LayerButton>
|
|
124
|
+
</ContextMenu.Trigger>
|
|
125
|
+
<ContextMenu.Content class="w-72">
|
|
126
|
+
<BackgroundMenuContent {editor} page={editor.activePage} />
|
|
127
|
+
</ContextMenu.Content>
|
|
128
|
+
</ContextMenu.Root>
|
|
129
|
+
</div>
|
|
130
|
+
</SidebarWrapper>
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import TextIcon from '@lucide/svelte/icons/type';
|
|
3
3
|
import Button from '../../ui/button/button.svelte';
|
|
4
|
-
import {
|
|
4
|
+
import type { Editor } from '../editor.svelte.js';
|
|
5
5
|
import { buildParagraphHtml } from '../utils.js';
|
|
6
6
|
import SidebarTextTabButton from './sidebar-text-tab-button.svelte';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
interface Props {
|
|
9
|
+
editor: Editor;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { editor }: Props = $props();
|
|
9
13
|
|
|
10
14
|
const createTextBox = () => {
|
|
11
15
|
const fontSize = 80 / 0.75;
|
|
@@ -65,7 +69,7 @@
|
|
|
65
69
|
];
|
|
66
70
|
</script>
|
|
67
71
|
|
|
68
|
-
<div class="flex flex-col">
|
|
72
|
+
<div class="flex flex-col p-2">
|
|
69
73
|
<Button variant="default" class="w-full" onclick={createTextBox}>
|
|
70
74
|
<TextIcon />
|
|
71
75
|
Add a text box
|
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} & Exports;
|
|
5
|
-
(internal: unknown, props: {
|
|
6
|
-
$$events?: Events;
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
1
|
+
import type { Editor } from '../editor.svelte.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
editor: Editor;
|
|
13
4
|
}
|
|
14
|
-
declare const SidebarTextTab:
|
|
15
|
-
|
|
16
|
-
}, {}, {}, string>;
|
|
17
|
-
type SidebarTextTab = InstanceType<typeof SidebarTextTab>;
|
|
5
|
+
declare const SidebarTextTab: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type SidebarTextTab = ReturnType<typeof SidebarTextTab>;
|
|
18
7
|
export default SidebarTextTab;
|
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
import { Label } from '../../ui/label/index.js';
|
|
8
8
|
import UploadsImage from './uploads-image.svelte';
|
|
9
9
|
import { checkImage } from '../utils.js';
|
|
10
|
-
import {
|
|
10
|
+
import type { Editor } from '../editor.svelte.js';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
interface Props {
|
|
13
|
+
editor: Editor;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { editor }: Props = $props();
|
|
13
17
|
|
|
14
18
|
let open = $state(false);
|
|
15
19
|
let url = $state('');
|
|
@@ -44,11 +48,9 @@
|
|
|
44
48
|
};
|
|
45
49
|
</script>
|
|
46
50
|
|
|
47
|
-
<div class="relative flex h-full shrink-0 flex-col gap-2">
|
|
51
|
+
<div class="relative flex h-full shrink-0 flex-col gap-2 p-2">
|
|
48
52
|
{#if editor.fileDragged}
|
|
49
|
-
<div
|
|
50
|
-
class="bg-background absolute inset-0 z-10 flex h-full w-full flex-col items-center justify-center"
|
|
51
|
-
>
|
|
53
|
+
<div class="absolute inset-0 z-10 flex h-full w-full flex-col items-center justify-center">
|
|
52
54
|
<div class="grid gap-6">
|
|
53
55
|
<div class="grid justify-center">
|
|
54
56
|
<CloudUploadIcon class="m-auto h-8 w-8" />
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
<ImageIcon class="h-4 w-4" />
|
|
61
63
|
<p class="text-sm font-semibold">Images</p>
|
|
62
64
|
</div>
|
|
63
|
-
<p class="text-muted-foreground
|
|
65
|
+
<p class="text-xs text-muted-foreground">GIF, JPG, PNG</p>
|
|
64
66
|
</div>
|
|
65
67
|
</div>
|
|
66
68
|
</div>
|
|
@@ -91,7 +93,7 @@
|
|
|
91
93
|
class="col-span-3"
|
|
92
94
|
/>
|
|
93
95
|
{#if error}
|
|
94
|
-
<div class="
|
|
96
|
+
<div class="col-span-3 col-start-2 text-sm font-medium text-destructive">
|
|
95
97
|
Invalid
|
|
96
98
|
</div>
|
|
97
99
|
{/if}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Editor } from '../editor.svelte.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
editor: Editor;
|
|
4
|
+
}
|
|
5
|
+
declare const SidebarUploadsTab: import("svelte").Component<Props, {}, "">;
|
|
2
6
|
type SidebarUploadsTab = ReturnType<typeof SidebarUploadsTab>;
|
|
3
7
|
export default SidebarUploadsTab;
|
|
@@ -5,18 +5,19 @@
|
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
name: string;
|
|
8
|
+
onclose?: () => void;
|
|
8
9
|
children?: import('svelte').Snippet;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
let { name, children }: Props = $props();
|
|
12
|
+
let { name, children, onclose = () => (editor.activeSidebarPopup = null) }: Props = $props();
|
|
12
13
|
|
|
13
14
|
const editor = getEditorContext();
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
17
|
<div class="flex h-full select-none flex-col gap-2">
|
|
17
|
-
<div class="flex items-center justify-between">
|
|
18
|
+
<div class="flex items-center justify-between p-2">
|
|
18
19
|
<div class="text-sm font-bold">{name}</div>
|
|
19
|
-
<Button variant="ghost" size="icon" onclick={
|
|
20
|
+
<Button variant="ghost" size="icon" onclick={onclose}>
|
|
20
21
|
<XIcon />
|
|
21
22
|
</Button>
|
|
22
23
|
</div>
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
import SidebarUploadsTab from './sidebar-uploads-tab.svelte';
|
|
8
8
|
import type { Editor } from '../editor.svelte.js';
|
|
9
9
|
import type { ActiveSidebarTab } from '../types.js';
|
|
10
|
-
import PositionSidebar from './position-sidebar.
|
|
10
|
+
import { PositionSidebar } from './position-sidebar/index.js';
|
|
11
11
|
import { ColorSidebar } from './color-sidebar/index.js';
|
|
12
12
|
import { FontSidebar } from './font-sidebar/index.js';
|
|
13
|
+
import { TransitionSidebar } from './transition-sidebar/index.js';
|
|
13
14
|
import ImageCropSidebar from './image-crop-sidebar.svelte';
|
|
14
15
|
|
|
15
16
|
interface Props {
|
|
@@ -64,19 +65,25 @@
|
|
|
64
65
|
</div>
|
|
65
66
|
|
|
66
67
|
{#if editor.activeSidebarPopup || editor.activeSidebarTab || editor.imageCropLayer}
|
|
67
|
-
<div class="
|
|
68
|
+
<div class="h-full w-80 shrink-0 overflow-y-auto border-r border-gray-200">
|
|
68
69
|
{#if editor.imageCropLayer}
|
|
69
|
-
<ImageCropSidebar layer={editor.imageCropLayer} />
|
|
70
|
-
{:else if editor.activeSidebarPopup
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
<ImageCropSidebar {editor} layer={editor.imageCropLayer} />
|
|
71
|
+
{:else if editor.activeSidebarPopup}
|
|
72
|
+
{#if editor.activeSidebarPopup === 'position'}
|
|
73
|
+
<PositionSidebar {editor} />
|
|
74
|
+
{:else if editor.activeSidebarPopup === 'font'}
|
|
75
|
+
<FontSidebar {editor} />
|
|
76
|
+
{:else if editor.activeSidebarPopup.indexOf('Color') > 0}
|
|
77
|
+
<ColorSidebar {editor} />
|
|
78
|
+
{:else if editor.activeSidebarPopup === 'transition'}
|
|
79
|
+
<TransitionSidebar {editor} />
|
|
80
|
+
{/if}
|
|
81
|
+
{:else if editor.activeSidebarTab}
|
|
82
|
+
{#if editor.activeSidebarTab === 'text'}
|
|
83
|
+
<SidebarTextTab {editor} />
|
|
84
|
+
{:else if editor.activeSidebarTab === 'uploads'}
|
|
85
|
+
<SidebarUploadsTab {editor} />
|
|
86
|
+
{/if}
|
|
80
87
|
{/if}
|
|
81
88
|
</div>
|
|
82
89
|
{/if}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import XIcon from '@lucide/svelte/icons/x';
|
|
3
|
+
import Button from '../../../ui/button/button.svelte';
|
|
4
|
+
import type { Editor } from '../../editor.svelte.js';
|
|
5
|
+
import type {
|
|
6
|
+
HistoryAction,
|
|
7
|
+
PageTransition,
|
|
8
|
+
PageTransitionDirection,
|
|
9
|
+
PageTransitionFlow,
|
|
10
|
+
} from '../../types.js';
|
|
11
|
+
import SidebarWrapper from '../sidebar-wrapper.svelte';
|
|
12
|
+
import { Slider } from '../../../ui/slider/index.js';
|
|
13
|
+
import { Input } from '../../../ui/input/index.js';
|
|
14
|
+
import { createDebouncedCallback } from '../../../../utils.js';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
editor: Editor;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { editor }: Props = $props();
|
|
21
|
+
|
|
22
|
+
let duration = $derived(editor.activePage.transition?.duration || 500);
|
|
23
|
+
|
|
24
|
+
const setDuration = (value: string | number, debounce = false) => {
|
|
25
|
+
if (!editor.activePage.transition) return;
|
|
26
|
+
|
|
27
|
+
value = Number(value);
|
|
28
|
+
duration = value * 1000;
|
|
29
|
+
|
|
30
|
+
if (editor.activePage.transition) {
|
|
31
|
+
setTransition({ ...editor.activePage.transition, duration }, debounce);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const directions: PageTransitionDirection[] = ['left', 'right', 'up', 'down'];
|
|
36
|
+
const flows: PageTransitionFlow[] = ['in', 'out'];
|
|
37
|
+
|
|
38
|
+
const transitionButtons: { type: string; label: string; getState: () => PageTransition }[] = [
|
|
39
|
+
{
|
|
40
|
+
type: 'fade',
|
|
41
|
+
label: 'Fade',
|
|
42
|
+
getState: () => ({ type: 'fade', duration }),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'slide',
|
|
46
|
+
label: 'Slide',
|
|
47
|
+
getState: () => ({ type: 'slide', duration, direction: 'left' }),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'circleWipe',
|
|
51
|
+
label: 'Circle Wipe',
|
|
52
|
+
getState: () => ({ type: 'circleWipe', duration, flow: 'in' }),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'lineWipe',
|
|
56
|
+
label: 'Line Wipe',
|
|
57
|
+
getState: () => ({ type: 'lineWipe', duration, direction: 'left' }),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'stack',
|
|
61
|
+
label: 'Stack',
|
|
62
|
+
getState: () => ({ type: 'stack', duration, direction: 'down' }),
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
let undo: object | null = null;
|
|
67
|
+
|
|
68
|
+
const debouncedUpdates = createDebouncedCallback((actions) => {
|
|
69
|
+
editor.historyPush(actions);
|
|
70
|
+
undo = null;
|
|
71
|
+
}, 500);
|
|
72
|
+
|
|
73
|
+
const setTransition = (value: PageTransition | null, debounce = false) => {
|
|
74
|
+
if (!undo) {
|
|
75
|
+
undo = { transition: $state.snapshot(editor.activePage.transition) };
|
|
76
|
+
}
|
|
77
|
+
const action: HistoryAction = {
|
|
78
|
+
type: 'pageUpdate',
|
|
79
|
+
pageId: editor.activePage.id,
|
|
80
|
+
undo,
|
|
81
|
+
redo: { transition: value },
|
|
82
|
+
};
|
|
83
|
+
if (debounce) {
|
|
84
|
+
debouncedUpdates(action);
|
|
85
|
+
} else {
|
|
86
|
+
editor.pageTransitionPreview = value;
|
|
87
|
+
editor.historyPush(action);
|
|
88
|
+
undo = null;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const allPagesHaveSameTransition = $derived.by(() => {
|
|
93
|
+
if (!editor.pages.length) return true;
|
|
94
|
+
|
|
95
|
+
const firstPageTransition = editor.pages[0].transition || {};
|
|
96
|
+
const firstPageTransitionKeys = Object.keys(firstPageTransition);
|
|
97
|
+
return editor.pages.every((page) => {
|
|
98
|
+
const transition = page.transition || {};
|
|
99
|
+
|
|
100
|
+
const keys = Object.keys(transition);
|
|
101
|
+
if (keys.length !== firstPageTransitionKeys.length) return false;
|
|
102
|
+
|
|
103
|
+
return keys.every(
|
|
104
|
+
(key) =>
|
|
105
|
+
key in firstPageTransition &&
|
|
106
|
+
(transition as Record<string, unknown>)[key] ===
|
|
107
|
+
(firstPageTransition as Record<string, unknown>)[key],
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const setTransitionToAllPages = () => {
|
|
113
|
+
const redo = { transition: $state.snapshot(editor.activePage.transition) };
|
|
114
|
+
editor.historyPush(
|
|
115
|
+
editor.pages.map((page) => ({
|
|
116
|
+
type: 'pageUpdate',
|
|
117
|
+
pageId: page.id,
|
|
118
|
+
undo: { transition: $state.snapshot(page.transition) },
|
|
119
|
+
redo,
|
|
120
|
+
})),
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// close sidebar if there is no next page
|
|
125
|
+
$effect(() => {
|
|
126
|
+
const nextPage = editor.sortedPages.find((p) => p.sortOrder > editor.activePage.sortOrder);
|
|
127
|
+
if (!nextPage) {
|
|
128
|
+
editor.activeSidebarPopup = null;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<SidebarWrapper name="Transition">
|
|
134
|
+
<div class="grid grid-cols-3 gap-2 p-2">
|
|
135
|
+
<Button
|
|
136
|
+
variant="outline"
|
|
137
|
+
size="vague"
|
|
138
|
+
class="aspect-square"
|
|
139
|
+
active={!editor.activePage.transition}
|
|
140
|
+
onclick={() => setTransition(null)}
|
|
141
|
+
>
|
|
142
|
+
<XIcon />
|
|
143
|
+
</Button>
|
|
144
|
+
|
|
145
|
+
{#each transitionButtons as button}
|
|
146
|
+
<Button
|
|
147
|
+
variant="outline"
|
|
148
|
+
size="vague"
|
|
149
|
+
class="aspect-square"
|
|
150
|
+
active={editor.activePage.transition?.type === button.type}
|
|
151
|
+
onclick={() => setTransition(button.getState())}
|
|
152
|
+
onmouseenter={() =>
|
|
153
|
+
(editor.pageTransitionPreview =
|
|
154
|
+
editor.activePage.transition?.type === button.type
|
|
155
|
+
? editor.activePage.transition
|
|
156
|
+
: button.getState())}
|
|
157
|
+
onmouseleave={() => (editor.pageTransitionPreview = null)}
|
|
158
|
+
>
|
|
159
|
+
{button.label}
|
|
160
|
+
</Button>
|
|
161
|
+
{/each}
|
|
162
|
+
|
|
163
|
+
{#if editor.activePage.transition}
|
|
164
|
+
{@const transition = editor.activePage.transition}
|
|
165
|
+
{@const buttonIndex =
|
|
166
|
+
transitionButtons.findIndex((button) => button.type === transition.type) + 1}
|
|
167
|
+
{@const rowIndex = Math.floor(buttonIndex / 3) + 1}
|
|
168
|
+
{@const colIndex = Math.floor(buttonIndex % 3) + 1}
|
|
169
|
+
<div class="col-span-full" style:grid-row={rowIndex + 1}>
|
|
170
|
+
<div class="grid grid-cols-3 gap-2">
|
|
171
|
+
<div
|
|
172
|
+
class="h-0 w-0 justify-self-center border-b-border"
|
|
173
|
+
style:border-bottom-width="10px"
|
|
174
|
+
style:border-left="10px solid transparent"
|
|
175
|
+
style:border-right="10px solid transparent"
|
|
176
|
+
style:grid-column="{colIndex} / {colIndex + 1}"
|
|
177
|
+
></div>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="mb-4 rounded-md border border-border p-2">
|
|
180
|
+
<div class="py-2">
|
|
181
|
+
<div class="text-sm font-medium">Duration (seconds)</div>
|
|
182
|
+
<div class="flex items-center gap-2">
|
|
183
|
+
<div class="grow">
|
|
184
|
+
<Slider
|
|
185
|
+
type="single"
|
|
186
|
+
value={duration / 1000}
|
|
187
|
+
min={0.1}
|
|
188
|
+
max={2.5}
|
|
189
|
+
step={0.01}
|
|
190
|
+
onValueChange={(value) => setDuration(value, true)}
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
<Input
|
|
194
|
+
class="w-12 bg-transparent p-0 text-center font-semibold leading-none [&::-webkit-inner-spin-button]:appearance-none"
|
|
195
|
+
inputmode="decimal"
|
|
196
|
+
placeholder="--"
|
|
197
|
+
value={duration / 1000}
|
|
198
|
+
onchange={(e) => setDuration(e.currentTarget.value)}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
{#if ['slide', 'lineWipe', 'circleWipe', 'stack'].includes(transition.type)}
|
|
203
|
+
<div class="flex flex-col gap-2 py-2">
|
|
204
|
+
<div class="text-sm font-medium">Direction</div>
|
|
205
|
+
<div class="grid grid-flow-col gap-2">
|
|
206
|
+
{#if transition.type === 'slide' || transition.type === 'lineWipe' || transition.type === 'stack'}
|
|
207
|
+
{#each directions as direction}
|
|
208
|
+
<Button
|
|
209
|
+
variant="outline"
|
|
210
|
+
active={transition.direction === direction}
|
|
211
|
+
onclick={() => {
|
|
212
|
+
setTransition({ ...transition, direction });
|
|
213
|
+
}}
|
|
214
|
+
onmouseenter={() => {
|
|
215
|
+
editor.pageTransitionPreview = { ...transition, direction };
|
|
216
|
+
}}
|
|
217
|
+
onmouseleave={() => {
|
|
218
|
+
editor.pageTransitionPreview = null;
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
{direction}
|
|
222
|
+
</Button>
|
|
223
|
+
{/each}
|
|
224
|
+
{:else if transition.type === 'circleWipe'}
|
|
225
|
+
{#each flows as flow}
|
|
226
|
+
<Button
|
|
227
|
+
variant="outline"
|
|
228
|
+
active={transition.flow === flow}
|
|
229
|
+
onclick={() => {
|
|
230
|
+
setTransition({ ...transition, flow });
|
|
231
|
+
}}
|
|
232
|
+
onmouseenter={() => {
|
|
233
|
+
editor.pageTransitionPreview = { ...transition, flow };
|
|
234
|
+
}}
|
|
235
|
+
onmouseleave={() => {
|
|
236
|
+
editor.pageTransitionPreview = null;
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
{flow}
|
|
240
|
+
</Button>
|
|
241
|
+
{/each}
|
|
242
|
+
{/if}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
{/if}
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
{/if}
|
|
249
|
+
</div>
|
|
250
|
+
{#if !allPagesHaveSameTransition}
|
|
251
|
+
<div class="p-2">
|
|
252
|
+
<Button class="w-full" variant="outline" onclick={() => setTransitionToAllPages()}>
|
|
253
|
+
Apply between all pages
|
|
254
|
+
</Button>
|
|
255
|
+
</div>
|
|
256
|
+
{/if}
|
|
257
|
+
</SidebarWrapper>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Editor } from '../../editor.svelte.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
editor: Editor;
|
|
4
|
+
}
|
|
5
|
+
declare const TransitionSidebar: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type TransitionSidebar = ReturnType<typeof TransitionSidebar>;
|
|
7
|
+
export default TransitionSidebar;
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
<div class="absolute h-full w-full">
|
|
14
14
|
{#each guides as guide (guide.id)}
|
|
15
15
|
{#if guide.type === 'box'}
|
|
16
|
-
<div class="border-primary
|
|
16
|
+
<div class="absolute border border-primary" style:inset="{guide.inset * zoom}px"></div>
|
|
17
17
|
{:else if guide.type === 'vertical'}
|
|
18
18
|
<div
|
|
19
19
|
class={cn(
|
|
20
|
-
'
|
|
20
|
+
'absolute h-full border-l border-primary',
|
|
21
21
|
guide.style === 'dashed' && 'border-dashed',
|
|
22
22
|
guide.style === 'dotted' && 'border-dotted',
|
|
23
23
|
)}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
{:else if guide.type === 'horizontal'}
|
|
29
29
|
<div
|
|
30
30
|
class={cn(
|
|
31
|
-
'
|
|
31
|
+
'absolute w-full border-t border-primary',
|
|
32
32
|
guide.style === 'dashed' && 'border-dashed',
|
|
33
33
|
guide.style === 'dotted' && 'border-dotted',
|
|
34
34
|
)}
|