@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.
Files changed (96) hide show
  1. package/dist/components/editor/active-layers.svelte +2 -2
  2. package/dist/components/editor/design.svelte +92 -0
  3. package/dist/components/editor/design.svelte.d.ts +8 -0
  4. package/dist/components/editor/editor.svelte +1 -1
  5. package/dist/components/editor/editor.svelte.js +14 -2
  6. package/dist/components/editor/header.svelte +1 -1
  7. package/dist/components/editor/index.d.ts +2 -1
  8. package/dist/components/editor/index.js +2 -1
  9. package/dist/components/editor/layers/active-layer-border.svelte +1 -1
  10. package/dist/components/editor/layers/buttons/border-button/border-button-colors.svelte +1 -1
  11. package/dist/components/editor/layers/controls/corner-scale-control/corner-scale-control.svelte +1 -1
  12. package/dist/components/editor/layers/controls/rotate-control/rotate-control.svelte +1 -1
  13. package/dist/components/editor/layers/controls/side-scale-control/side-scale-control.svelte +1 -1
  14. package/dist/components/editor/layers/layer-button.svelte +1 -1
  15. package/dist/components/editor/layers/types/background/background-layer-buttons.svelte +1 -1
  16. package/dist/components/editor/layers/types/image/controls/image-rotate-control/image-rotate-control.svelte +1 -1
  17. package/dist/components/editor/layers/types/image/controls/image-scale-control/image-scale-control.svelte +1 -1
  18. package/dist/components/editor/layers/types/image/image-layer-crop.svelte +2 -2
  19. package/dist/components/editor/page-editor.svelte +7 -0
  20. package/dist/components/editor/{pages-navigation/page-preview.svelte → page-preview.svelte} +4 -4
  21. package/dist/components/editor/{pages-navigation/page-preview.svelte.d.ts → page-preview.svelte.d.ts} +2 -2
  22. package/dist/components/editor/page-transition-preview.svelte +34 -0
  23. package/dist/components/editor/page-transition-preview.svelte.d.ts +9 -0
  24. package/dist/components/editor/page-with-transition.svelte +48 -0
  25. package/dist/components/editor/page-with-transition.svelte.d.ts +10 -0
  26. package/dist/components/editor/page.svelte +12 -19
  27. package/dist/components/editor/pages-navigation/pages-navigation-item.svelte +80 -20
  28. package/dist/components/editor/pages-navigation/pages-navigation.svelte +7 -4
  29. package/dist/components/editor/sidebar/color-sidebar/color-sidebar-color.svelte +2 -2
  30. package/dist/components/editor/sidebar/color-sidebar/color-sidebar-gradient-picker.svelte +5 -5
  31. package/dist/components/editor/sidebar/color-sidebar/color-sidebar.svelte +91 -81
  32. package/dist/components/editor/sidebar/color-sidebar/color-sidebar.svelte.d.ts +5 -1
  33. package/dist/components/editor/sidebar/font-sidebar/font-sidebar.svelte +10 -6
  34. package/dist/components/editor/sidebar/font-sidebar/font-sidebar.svelte.d.ts +5 -1
  35. package/dist/components/editor/sidebar/image-crop-sidebar.svelte +28 -37
  36. package/dist/components/editor/sidebar/image-crop-sidebar.svelte.d.ts +2 -0
  37. package/dist/components/editor/sidebar/position-sidebar/index.d.ts +2 -0
  38. package/dist/components/editor/sidebar/position-sidebar/index.js +2 -0
  39. package/dist/components/editor/sidebar/position-sidebar/position-sidebar.svelte +130 -0
  40. package/dist/components/editor/sidebar/position-sidebar/position-sidebar.svelte.d.ts +7 -0
  41. package/dist/components/editor/sidebar/sidebar-text-tab.svelte +7 -3
  42. package/dist/components/editor/sidebar/sidebar-text-tab.svelte.d.ts +5 -16
  43. package/dist/components/editor/sidebar/sidebar-uploads-tab.svelte +10 -8
  44. package/dist/components/editor/sidebar/sidebar-uploads-tab.svelte.d.ts +5 -1
  45. package/dist/components/editor/sidebar/sidebar-wrapper.svelte +4 -3
  46. package/dist/components/editor/sidebar/sidebar-wrapper.svelte.d.ts +1 -0
  47. package/dist/components/editor/sidebar/sidebar.svelte +20 -13
  48. package/dist/components/editor/sidebar/transition-sidebar/index.d.ts +2 -0
  49. package/dist/components/editor/sidebar/transition-sidebar/index.js +2 -0
  50. package/dist/components/editor/sidebar/transition-sidebar/transition-sidebar.svelte +257 -0
  51. package/dist/components/editor/sidebar/transition-sidebar/transition-sidebar.svelte.d.ts +7 -0
  52. package/dist/components/editor/snapping-guides.svelte +3 -3
  53. package/dist/components/editor/types.d.ts +40 -6
  54. package/dist/components/editor/utils.d.ts +2 -2
  55. package/dist/components/editor/utils.js +21 -0
  56. package/dist/components/ui/button/button.svelte +2 -1
  57. package/dist/components/ui/button/button.svelte.d.ts +3 -0
  58. package/dist/components/ui/color-picker/color-picker-alpha-grid.svelte +2 -1
  59. package/dist/components/ui/color-picker/color-picker.svelte +6 -6
  60. package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte +1 -1
  61. package/dist/components/ui/context-menu/context-menu-content.svelte +3 -1
  62. package/dist/components/ui/context-menu/context-menu-group-heading.svelte +1 -1
  63. package/dist/components/ui/context-menu/context-menu-item.svelte +1 -1
  64. package/dist/components/ui/context-menu/context-menu-radio-item.svelte +1 -1
  65. package/dist/components/ui/context-menu/context-menu-separator.svelte +1 -1
  66. package/dist/components/ui/context-menu/context-menu-shortcut.svelte +1 -1
  67. package/dist/components/ui/context-menu/context-menu-sub-content.svelte +1 -1
  68. package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte +1 -1
  69. package/dist/components/ui/dialog/dialog-content.svelte +4 -2
  70. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
  71. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -0
  72. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
  73. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
  74. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  75. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
  76. package/dist/components/ui/input/input.svelte +1 -1
  77. package/dist/components/ui/slider/slider.svelte +3 -3
  78. package/dist/components/ui/tabs/index.d.ts +4 -4
  79. package/dist/components/ui/tabs/index.js +4 -4
  80. package/dist/components/ui/tabs/tabs-content.svelte +4 -4
  81. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +1 -1
  82. package/dist/components/ui/tabs/tabs-list.svelte +5 -9
  83. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +1 -1
  84. package/dist/components/ui/tabs/tabs-trigger.svelte +4 -4
  85. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +1 -1
  86. package/dist/transitions/circleWipe.d.ts +10 -0
  87. package/dist/transitions/circleWipe.js +16 -0
  88. package/dist/transitions/lineWipe.d.ts +9 -0
  89. package/dist/transitions/lineWipe.js +17 -0
  90. package/dist/transitions/stack.d.ts +8 -0
  91. package/dist/transitions/stack.js +13 -0
  92. package/package.json +19 -19
  93. package/dist/components/editor/layers/types/image/image-layer-active.svelte +0 -36
  94. package/dist/components/editor/layers/types/image/image-layer-active.svelte.d.ts +0 -7
  95. package/dist/components/editor/sidebar/position-sidebar.svelte +0 -136
  96. 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>
@@ -0,0 +1,7 @@
1
+ import type { Editor } from '../../editor.svelte.js';
2
+ interface Props {
3
+ editor: Editor;
4
+ }
5
+ declare const PositionSidebar: import("svelte").Component<Props, {}, "">;
6
+ type PositionSidebar = ReturnType<typeof PositionSidebar>;
7
+ export default PositionSidebar;
@@ -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 { getEditorContext } from '../editor.svelte.js';
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
- const editor = getEditorContext();
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
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
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: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- [evt: string]: CustomEvent<any>;
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 { getEditorContext } from '../editor.svelte.js';
10
+ import type { Editor } from '../editor.svelte.js';
11
11
 
12
- const editor = getEditorContext();
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 text-xs">GIF, JPG, PNG</p>
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="text-destructive col-span-3 col-start-2 text-sm font-medium">
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
- declare const SidebarUploadsTab: import("svelte").Component<Record<string, never>, {}, "">;
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={() => (editor.activeSidebarPopup = null)}>
20
+ <Button variant="ghost" size="icon" onclick={onclose}>
20
21
  <XIcon />
21
22
  </Button>
22
23
  </div>
@@ -1,5 +1,6 @@
1
1
  interface Props {
2
2
  name: string;
3
+ onclose?: () => void;
3
4
  children?: import('svelte').Snippet;
4
5
  }
5
6
  declare const SidebarWrapper: import("svelte").Component<Props, {}, "">;
@@ -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.svelte';
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="bg-background h-full w-80 shrink-0 overflow-y-auto border-r border-gray-200 p-2">
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 === 'position'}
71
- <PositionSidebar />
72
- {:else if editor.activeSidebarPopup === 'font'}
73
- <FontSidebar />
74
- {:else if editor.activeSidebarPopup?.indexOf('Color')}
75
- <ColorSidebar />
76
- {:else if editor.activeSidebarTab === 'text'}
77
- <SidebarTextTab />
78
- {:else if editor.activeSidebarTab === 'uploads'}
79
- <SidebarUploadsTab />
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,2 @@
1
+ import TransitionSidebar from './transition-sidebar.svelte';
2
+ export { TransitionSidebar };
@@ -0,0 +1,2 @@
1
+ import TransitionSidebar from './transition-sidebar.svelte';
2
+ export { TransitionSidebar };
@@ -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 absolute border" style:inset="{guide.inset * zoom}px"></div>
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
- 'border-primary absolute h-full border-l',
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
- 'border-primary absolute w-full border-t',
31
+ 'absolute w-full border-t border-primary',
32
32
  guide.style === 'dashed' && 'border-dashed',
33
33
  guide.style === 'dotted' && 'border-dotted',
34
34
  )}