pixedi 1.0.0

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 (110) hide show
  1. package/.github/workflows/deploy.yml +54 -0
  2. package/.prettierr +9 -0
  3. package/.storybook/main.ts +16 -0
  4. package/.storybook/preview.ts +53 -0
  5. package/.vscode/extensions.json +6 -0
  6. package/README.md +75 -0
  7. package/eslint.config.js +22 -0
  8. package/index.html +13 -0
  9. package/package.json +57 -0
  10. package/pnpm-workspace.yaml +2 -0
  11. package/public/favicon.svg +1 -0
  12. package/src/app/App.tsx +10 -0
  13. package/src/app/entries/index.ts +145 -0
  14. package/src/app/providers/RouterProvider.tsx +6 -0
  15. package/src/app/providers/index.ts +1 -0
  16. package/src/app/router/AppRouter.tsx +12 -0
  17. package/src/app/router/index.ts +0 -0
  18. package/src/app/styles/index.css +4 -0
  19. package/src/features/gallery/GalleryPage.tsx +33 -0
  20. package/src/features/gallery/ImageEditPage.tsx +37 -0
  21. package/src/features/gallery/index.ts +2 -0
  22. package/src/main.tsx +10 -0
  23. package/src/shared/components/ImageCard/ImageCard.tsx +54 -0
  24. package/src/shared/components/ImageCard/index.ts +1 -0
  25. package/src/shared/components/ImageEditor/ImageEditor.tsx +48 -0
  26. package/src/shared/components/ImageEditor/assets/icons.tsx +114 -0
  27. package/src/shared/components/ImageEditor/constants.ts +118 -0
  28. package/src/shared/components/ImageEditor/crop/Crop.tsx +60 -0
  29. package/src/shared/components/ImageEditor/crop/CropBorders.tsx +28 -0
  30. package/src/shared/components/ImageEditor/crop/CropLines.tsx +8 -0
  31. package/src/shared/components/ImageEditor/crop/CropPointer.tsx +21 -0
  32. package/src/shared/components/ImageEditor/crop/CropPointers.tsx +44 -0
  33. package/src/shared/components/ImageEditor/crop/CropTools.tsx +138 -0
  34. package/src/shared/components/ImageEditor/crop/crop.css +180 -0
  35. package/src/shared/components/ImageEditor/crop/index.ts +8 -0
  36. package/src/shared/components/ImageEditor/crop/useCropInteraction.ts +230 -0
  37. package/src/shared/components/ImageEditor/eventBus.ts +8 -0
  38. package/src/shared/components/ImageEditor/frame/Frame.tsx +48 -0
  39. package/src/shared/components/ImageEditor/frame/frame.css +40 -0
  40. package/src/shared/components/ImageEditor/frame/index.ts +1 -0
  41. package/src/shared/components/ImageEditor/header/Header.tsx +81 -0
  42. package/src/shared/components/ImageEditor/header/header.css +27 -0
  43. package/src/shared/components/ImageEditor/header/index.ts +1 -0
  44. package/src/shared/components/ImageEditor/hooks/index.ts +5 -0
  45. package/src/shared/components/ImageEditor/hooks/useAbove.tsx +18 -0
  46. package/src/shared/components/ImageEditor/hooks/useBellow.tsx +18 -0
  47. package/src/shared/components/ImageEditor/hooks/useImageLoader.tsx +110 -0
  48. package/src/shared/components/ImageEditor/hooks/useImageProcessor.test.tsx +147 -0
  49. package/src/shared/components/ImageEditor/hooks/useImageProcessor.ts +225 -0
  50. package/src/shared/components/ImageEditor/hooks/useMobile.tsx +33 -0
  51. package/src/shared/components/ImageEditor/index.css +145 -0
  52. package/src/shared/components/ImageEditor/index.ts +1 -0
  53. package/src/shared/components/ImageEditor/provider/ImageEditorProvider.test.tsx +126 -0
  54. package/src/shared/components/ImageEditor/provider/ImageEditorProvider.tsx +107 -0
  55. package/src/shared/components/ImageEditor/provider/StoreContext.tsx +20 -0
  56. package/src/shared/components/ImageEditor/provider/initialState.ts +30 -0
  57. package/src/shared/components/ImageEditor/provider/useImageEditorContext.tsx +12 -0
  58. package/src/shared/components/ImageEditor/resize/ResizeTools.tsx +183 -0
  59. package/src/shared/components/ImageEditor/resize/index.ts +1 -0
  60. package/src/shared/components/ImageEditor/resize/resize.css +56 -0
  61. package/src/shared/components/ImageEditor/sidebar/Sidebar.tsx +54 -0
  62. package/src/shared/components/ImageEditor/sidebar/SidebarCrop.tsx +57 -0
  63. package/src/shared/components/ImageEditor/sidebar/SidebarPresets.tsx +54 -0
  64. package/src/shared/components/ImageEditor/sidebar/SidebarResize.tsx +26 -0
  65. package/src/shared/components/ImageEditor/sidebar/index.ts +1 -0
  66. package/src/shared/components/ImageEditor/sidebar/sidebar.css +101 -0
  67. package/src/shared/components/ImageEditor/types.ts +59 -0
  68. package/src/shared/components/ImageEditor/ui/button/Button.stories.tsx +42 -0
  69. package/src/shared/components/ImageEditor/ui/button/Button.tsx +21 -0
  70. package/src/shared/components/ImageEditor/ui/button/button.css +69 -0
  71. package/src/shared/components/ImageEditor/ui/button-crop/ButtonCrop.stories.tsx +26 -0
  72. package/src/shared/components/ImageEditor/ui/button-crop/ButtonCrop.tsx +29 -0
  73. package/src/shared/components/ImageEditor/ui/button-crop/button-crop.css +38 -0
  74. package/src/shared/components/ImageEditor/ui/index.ts +6 -0
  75. package/src/shared/components/ImageEditor/ui/input/Input.stories.tsx +45 -0
  76. package/src/shared/components/ImageEditor/ui/input/Input.tsx +20 -0
  77. package/src/shared/components/ImageEditor/ui/input/input.css +48 -0
  78. package/src/shared/components/ImageEditor/ui/input-pixel/InputPixel.stories.tsx +33 -0
  79. package/src/shared/components/ImageEditor/ui/input-pixel/InputPixel.tsx +23 -0
  80. package/src/shared/components/ImageEditor/ui/input-pixel/input-pixel.css +24 -0
  81. package/src/shared/components/ImageEditor/ui/select/Select.stories.tsx +67 -0
  82. package/src/shared/components/ImageEditor/ui/select/Select.tsx +161 -0
  83. package/src/shared/components/ImageEditor/ui/select/select.css +147 -0
  84. package/src/shared/components/ImageEditor/ui/separator/Separator.stories.tsx +37 -0
  85. package/src/shared/components/ImageEditor/ui/separator/Separator.tsx +27 -0
  86. package/src/shared/components/ImageEditor/ui/separator/separator.css +19 -0
  87. package/src/shared/components/ImageEditor/utils.test.ts +136 -0
  88. package/src/shared/components/ImageEditor/utils.ts +497 -0
  89. package/src/shared/components/ui/index.ts +1 -0
  90. package/src/shared/components/ui/preloader.tsx +78 -0
  91. package/src/shared/config/index.ts +3 -0
  92. package/src/shared/config/routes.ts +4 -0
  93. package/src/shared/features/gallery/GalleryPage.tsx +33 -0
  94. package/src/shared/features/gallery/ImageEditPage.tsx +37 -0
  95. package/src/shared/features/gallery/index.ts +2 -0
  96. package/src/shared/hooks/index.ts +1 -0
  97. package/src/shared/hooks/useImagePreload.tsx +49 -0
  98. package/src/shared/types/index.ts +11 -0
  99. package/src/test/setup.ts +14 -0
  100. package/src/virtual-css-injected-by-js.d.ts +8 -0
  101. package/src/widget.tsx +119 -0
  102. package/tsconfig.app.json +29 -0
  103. package/tsconfig.json +12 -0
  104. package/tsconfig.lib.json +18 -0
  105. package/tsconfig.node.json +23 -0
  106. package/vite-env.d.ts +1 -0
  107. package/vite.config.ts +24 -0
  108. package/vite.lib.config.ts +33 -0
  109. package/vite.widget.config.ts +39 -0
  110. package/wrangler.json +5 -0
@@ -0,0 +1,11 @@
1
+ export type Image = {
2
+ id: string;
3
+ author: string;
4
+ title: string;
5
+ width: number;
6
+ height: number;
7
+ ext: string;
8
+ fileName: string;
9
+ thumbnail: string;
10
+ original: string;
11
+ };
@@ -0,0 +1,14 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { vi } from "vitest";
3
+
4
+ vi.stubGlobal("createImageBitmap", vi.fn());
5
+ vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue(null);
6
+ vi.spyOn(HTMLCanvasElement.prototype, "toDataURL").mockReturnValue("");
7
+
8
+ if (!globalThis.ImageBitmap) {
9
+ class ImageBitmapMock {
10
+ close() {}
11
+ }
12
+
13
+ vi.stubGlobal("ImageBitmap", ImageBitmapMock);
14
+ }
@@ -0,0 +1,8 @@
1
+ declare module "virtual:css-injected-by-js" {
2
+ interface CSSInjectionOptions {
3
+ target?: HTMLElement | ShadowRoot;
4
+ }
5
+
6
+ export function injectCSS(options?: CSSInjectionOptions): void;
7
+ export function removeCSS(options?: CSSInjectionOptions): void;
8
+ }
package/src/widget.tsx ADDED
@@ -0,0 +1,119 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { injectCSS, removeCSS } from "virtual:css-injected-by-js";
4
+ import type { FuncSaveArgs } from "@/shared/components/ImageEditor/types";
5
+ import { ImageEditor } from "@/shared/components/ImageEditor";
6
+
7
+ type WidgetTheme = "light" | "dark";
8
+
9
+ interface WidgetOptions {
10
+ containerId: string;
11
+ image: string;
12
+ onSave: FuncSaveArgs;
13
+ onBack: () => void;
14
+ theme?: WidgetTheme;
15
+ }
16
+
17
+ interface ImageEditorWidgetInstance {
18
+ destroy: () => void;
19
+ }
20
+
21
+ interface ImageEditorWidget {
22
+ init: (options: WidgetOptions) => ImageEditorWidgetInstance | undefined;
23
+ }
24
+
25
+ interface ActiveWidget {
26
+ root: ReactDOM.Root;
27
+ shadowRoot: ShadowRoot;
28
+ mountElement: HTMLDivElement;
29
+ container: HTMLElement;
30
+ previousTheme: string | null;
31
+ }
32
+
33
+ declare global {
34
+ interface Window {
35
+ ImageEditorWidget?: ImageEditorWidget;
36
+ }
37
+ }
38
+
39
+ let activeWidget: ActiveWidget | null = null;
40
+
41
+ const destroyActiveWidget = () => {
42
+ if (!activeWidget) {
43
+ return;
44
+ }
45
+
46
+ activeWidget.root.unmount();
47
+ removeCSS({ target: activeWidget.shadowRoot });
48
+ activeWidget.mountElement.remove();
49
+
50
+ if (activeWidget.previousTheme === null) {
51
+ activeWidget.container.removeAttribute("data-theme");
52
+ } else {
53
+ activeWidget.container.setAttribute(
54
+ "data-theme",
55
+ activeWidget.previousTheme,
56
+ );
57
+ }
58
+
59
+ activeWidget = null;
60
+ };
61
+
62
+ const ImageEditorWidget: ImageEditorWidget = {
63
+ init: (options) => {
64
+ const container = document.getElementById(options.containerId);
65
+ if (!container) return;
66
+
67
+ destroyActiveWidget();
68
+
69
+ let shadowRoot = container.shadowRoot;
70
+ if (!shadowRoot) {
71
+ try {
72
+ shadowRoot = container.attachShadow({ mode: "open" });
73
+ } catch {
74
+ return;
75
+ }
76
+ }
77
+
78
+ const previousTheme = container.getAttribute("data-theme");
79
+ container.setAttribute("data-theme", options.theme ?? "light");
80
+
81
+ injectCSS({ target: shadowRoot });
82
+
83
+ const mountElement = document.createElement("div");
84
+ shadowRoot.append(mountElement);
85
+
86
+ const root = ReactDOM.createRoot(mountElement);
87
+ activeWidget = {
88
+ root,
89
+ shadowRoot,
90
+ mountElement,
91
+ container,
92
+ previousTheme,
93
+ };
94
+
95
+ root.render(
96
+ <React.StrictMode>
97
+ <ImageEditor
98
+ image={options.image}
99
+ onSave={options.onSave}
100
+ onBack={options.onBack}
101
+ />
102
+ </React.StrictMode>,
103
+ );
104
+
105
+ return {
106
+ destroy: () => {
107
+ if (activeWidget?.root === root) {
108
+ destroyActiveWidget();
109
+ }
110
+ },
111
+ };
112
+ },
113
+ };
114
+
115
+ if (typeof window !== "undefined") {
116
+ window.ImageEditorWidget = ImageEditorWidget;
117
+ }
118
+
119
+ export default ImageEditorWidget;
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023", "DOM"],
6
+ "module": "esnext",
7
+ "types": ["vite/client"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+
18
+ /* Linting */
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+
24
+ "paths": {
25
+ "@/*": ["./src/*"]
26
+ }
27
+ },
28
+ "include": ["src"]
29
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ],
7
+ "compilerOptions": {
8
+ "paths": {
9
+ "@/*": ["./src/*"]
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "./tsconfig.app.json",
3
+ "compilerOptions": {
4
+ "declaration": true,
5
+ "emitDeclarationOnly": true,
6
+ "outDir": "./dist/lib",
7
+ "rootDir": "."
8
+ },
9
+ "include": ["src/shared/components/ImageEditor/**/*"],
10
+ "exclude": [
11
+ "src/shared/components/ImageEditor/**/*.stories.tsx",
12
+ "src/shared/components/ImageEditor/**/*.stories.ts",
13
+ "src/shared/components/ImageEditor/**/*.test.tsx",
14
+ "src/shared/components/ImageEditor/**/*.test.ts",
15
+ "src/shared/components/ImageEditor/**/*.spec.tsx",
16
+ "src/shared/components/ImageEditor/**/*.spec.ts"
17
+ ]
18
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023"],
6
+ "types": ["node"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "module": "nodenext",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "erasableSyntaxOnly": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["vite.config.ts"]
23
+ }
package/vite-env.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
package/vite.config.ts ADDED
@@ -0,0 +1,24 @@
1
+ import path from "path";
2
+ import { defineConfig } from "vitest/config";
3
+ import react from "@vitejs/plugin-react";
4
+
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react()],
8
+ server: {
9
+ port: 3000,
10
+ },
11
+ resolve: {
12
+ alias: {
13
+ "@": path.resolve(__dirname, "./src"),
14
+ },
15
+ },
16
+ build: {
17
+ outDir: "dist/app",
18
+ minify: true,
19
+ },
20
+ test: {
21
+ environment: "jsdom",
22
+ setupFiles: ["./src/test/setup.ts"],
23
+ },
24
+ });
@@ -0,0 +1,33 @@
1
+ import path from "path";
2
+ import { defineConfig } from "vite";
3
+ import react from "@vitejs/plugin-react";
4
+ import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";
5
+
6
+ export default defineConfig({
7
+ plugins: [react(), cssInjectedByJsPlugin()],
8
+ publicDir: false,
9
+ resolve: {
10
+ alias: {
11
+ "@": path.resolve(__dirname, "./src"),
12
+ },
13
+ },
14
+ build: {
15
+ outDir: "dist/lib",
16
+ emptyOutDir: false,
17
+ lib: {
18
+ entry: "src/shared/components/ImageEditor/ImageEditor.tsx",
19
+ name: "Pixedi",
20
+ fileName: (format) => `index.${format === "es" ? "js" : "umd.js"}`,
21
+ formats: ["es", "umd"],
22
+ },
23
+ rollupOptions: {
24
+ external: ["react", "react-dom"],
25
+ output: {
26
+ globals: {
27
+ react: "React",
28
+ "react-dom": "ReactDOM",
29
+ },
30
+ },
31
+ },
32
+ },
33
+ });
@@ -0,0 +1,39 @@
1
+ import path from "path";
2
+ import { defineConfig } from "vite";
3
+ import react from "@vitejs/plugin-react";
4
+ import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ react(),
9
+ cssInjectedByJsPlugin({
10
+ attributes: { "data-image-editor-widget": "" },
11
+ }),
12
+ ],
13
+ publicDir: false,
14
+ define: {
15
+ "process.env.NODE_ENV": '"production"',
16
+ },
17
+ resolve: {
18
+ alias: {
19
+ "@": path.resolve(__dirname, "./src"),
20
+ },
21
+ },
22
+ build: {
23
+ outDir: "dist/widget",
24
+ emptyOutDir: false,
25
+ minify: "oxc",
26
+ lib: {
27
+ entry: "src/widget.tsx",
28
+ name: "ImageEditorWidget",
29
+ formats: ["umd"],
30
+ },
31
+ rolldownOptions: {
32
+ output: {
33
+ comments: { legal: false },
34
+ entryFileNames: "pixedi-widget.js",
35
+ assetFileNames: "[name].[ext]",
36
+ },
37
+ },
38
+ },
39
+ });
package/wrangler.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ "name": "pixedi",
4
+ "pages_build_output_dir": "dist"
5
+ }