├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── actions │ └── setup-node │ │ └── action.yml └── workflows │ └── ci.yml ├── .gitignore ├── .husky └── pre-commit ├── .node-version ├── .prettierignore ├── .scaffdog ├── config.js ├── ui-component.md └── ui-icon.md ├── .storybook ├── main.ts ├── preview-head.html └── preview.tsx ├── LICENSE ├── README.md ├── docs └── images │ ├── close.png │ ├── open.png │ └── viewer.png ├── eslint.config.js ├── index.html ├── package.json ├── public ├── actual │ ├── nest │ │ ├── deep │ │ │ └── sample.png │ │ └── sample.png │ ├── sample.1.png │ ├── sample.3.png │ ├── sample.png │ └── sample │ │ └── sample.png ├── detector.wasm ├── diff │ ├── nest │ │ ├── deep │ │ │ └── sample.png │ │ └── sample.png │ └── sample.png ├── expected │ ├── nest │ │ ├── deep │ │ │ └── sample.png │ │ └── sample.png │ ├── sample foo bar.png │ ├── sample.1.png │ ├── sample.2.png │ ├── sample.3.png │ └── sample.png └── worker.js ├── src ├── App │ ├── App.css.ts │ ├── App.tsx │ └── index.tsx ├── components │ ├── Card │ │ ├── Card.css.ts │ │ ├── Card.stories.tsx │ │ ├── Card.tsx │ │ └── index.ts │ ├── ChoiceGroup │ │ ├── ChoiceGroup.css.ts │ │ ├── ChoiceGroup.stories.tsx │ │ ├── ChoiceGroup.tsx │ │ ├── index.ts │ │ └── internal │ │ │ └── ChoiceButton │ │ │ ├── ChoiceButton.css.ts │ │ │ ├── ChoiceButton.tsx │ │ │ └── index.ts │ ├── Container │ │ ├── Container.css.ts │ │ ├── Container.tsx │ │ └── index.ts │ ├── Dialog │ │ ├── Dialog.css.ts │ │ ├── Dialog.stories.tsx │ │ ├── Dialog.tsx │ │ └── index.ts │ ├── Footer │ │ ├── Footer.css.ts │ │ ├── Footer.stories.tsx │ │ ├── Footer.tsx │ │ └── index.ts │ ├── Header │ │ ├── Header.css.ts │ │ ├── Header.stories.tsx │ │ ├── Header.tsx │ │ └── index.ts │ ├── HelpDialog │ │ ├── HelpDialog.css.ts │ │ ├── HelpDialog.stories.tsx │ │ ├── HelpDialog.tsx │ │ └── index.ts │ ├── IconButton │ │ ├── IconButton.css.ts │ │ ├── IconButton.stories.tsx │ │ ├── IconButton.tsx │ │ └── index.ts │ ├── Image │ │ ├── Image.css.ts │ │ ├── Image.stories.tsx │ │ ├── Image.tsx │ │ └── index.ts │ ├── Link │ │ ├── Link.tsx │ │ └── index.ts │ ├── List │ │ ├── Expandable.css.ts │ │ ├── Expandable.tsx │ │ ├── Item.css.ts │ │ ├── Item.tsx │ │ ├── List.css.ts │ │ ├── List.stories.tsx │ │ ├── List.tsx │ │ └── index.ts │ ├── Logo │ │ ├── Logo.css.ts │ │ ├── Logo.stories.tsx │ │ ├── Logo.tsx │ │ └── index.ts │ ├── Main │ │ ├── Main.css.ts │ │ ├── Main.tsx │ │ └── index.ts │ ├── Menu │ │ ├── Item.css.ts │ │ ├── Item.tsx │ │ ├── Menu.css.ts │ │ ├── Menu.stories.tsx │ │ ├── Menu.tsx │ │ └── index.ts │ ├── Notification │ │ ├── Notification.css.ts │ │ ├── Notification.stories.tsx │ │ ├── Notification.tsx │ │ └── index.tsx │ ├── PoweredBy │ │ ├── PoweredBy.css.ts │ │ ├── PoweredBy.stories.tsx │ │ ├── PoweredBy.tsx │ │ └── index.ts │ ├── SearchBox │ │ ├── SearchBox.css.ts │ │ ├── SearchBox.stories.tsx │ │ ├── SearchBox.tsx │ │ └── index.ts │ ├── Sidebar │ │ ├── Sidebar.tsx │ │ ├── index.ts │ │ ├── internal │ │ │ ├── Desktop │ │ │ │ ├── Desktop.css.ts │ │ │ │ ├── Desktop.tsx │ │ │ │ └── index.ts │ │ │ ├── Mobile │ │ │ │ ├── Mobile.css.ts │ │ │ │ ├── Mobile.tsx │ │ │ │ └── index.ts │ │ │ ├── SidebarInner │ │ │ │ ├── SidebarInner.css.ts │ │ │ │ ├── SidebarInner.tsx │ │ │ │ └── index.ts │ │ │ ├── Summary │ │ │ │ ├── Summary.tsx │ │ │ │ └── index.ts │ │ │ └── Toggle │ │ │ │ ├── Toggle.css.ts │ │ │ │ ├── Toggle.stories.tsx │ │ │ │ ├── Toggle.tsx │ │ │ │ └── index.ts │ │ └── types.ts │ ├── Sign │ │ ├── Sign.css.ts │ │ ├── Sign.stories.tsx │ │ ├── Sign.tsx │ │ └── index.ts │ ├── Slider │ │ ├── Slider.css.ts │ │ ├── Slider.stories.tsx │ │ ├── Slider.tsx │ │ └── index.ts │ ├── Snackbar │ │ ├── Snackbar.css.ts │ │ ├── Snackbar.stories.tsx │ │ ├── Snackbar.tsx │ │ └── index.ts │ ├── Spacer │ │ ├── Spacer.css.ts │ │ ├── Spacer.stories.tsx │ │ ├── Spacer.tsx │ │ └── index.ts │ ├── Spinner │ │ ├── Spinner.stories.tsx │ │ ├── Spinner.tsx │ │ └── index.ts │ ├── Switch │ │ ├── Switch.css.ts │ │ ├── Switch.stories.tsx │ │ ├── Switch.tsx │ │ └── index.ts │ ├── VGrid │ │ ├── VGrid.tsx │ │ └── index.ts │ ├── Viewer │ │ ├── Viewer.css.ts │ │ ├── Viewer.stories.tsx │ │ ├── Viewer.tsx │ │ ├── constants.ts │ │ ├── index.tsx │ │ └── internal │ │ │ └── ComparisonView │ │ │ ├── Blend.css.ts │ │ │ ├── Blend.tsx │ │ │ ├── ComparisonView.css.ts │ │ │ ├── ComparisonView.stories.tsx │ │ │ ├── ComparisonView.tsx │ │ │ ├── Diff.tsx │ │ │ ├── Markers.css.ts │ │ │ ├── Markers.tsx │ │ │ ├── Slide.css.ts │ │ │ ├── Slide.tsx │ │ │ ├── Toggle.css.ts │ │ │ ├── Toggle.tsx │ │ │ ├── TwoUp.css.ts │ │ │ ├── TwoUp.tsx │ │ │ ├── index.ts │ │ │ └── useComparisonImage.ts │ ├── VisuallyHidden │ │ ├── VisuallyHidden.css.ts │ │ ├── VisuallyHidden.stories.tsx │ │ ├── VisuallyHidden.tsx │ │ └── index.ts │ ├── icons │ │ ├── ArrowDownIcon.tsx │ │ ├── ArrowLeftIcon.tsx │ │ ├── ArrowRightIcon.tsx │ │ ├── ArrowUpIcon.tsx │ │ ├── CloseIcon.tsx │ │ ├── HelpIcon.tsx │ │ ├── LinkIcon.tsx │ │ ├── MoreIcon.tsx │ │ ├── SearchIcon.tsx │ │ ├── SignChangedIcon.tsx │ │ ├── SignDeletedIcon.tsx │ │ ├── SignNewIcon.tsx │ │ └── SignPassedIcon.tsx │ └── internal │ │ ├── BaseButton │ │ ├── BaseButton.css.ts │ │ ├── BaseButton.tsx │ │ └── index.ts │ │ ├── Collapse │ │ ├── Collapse.css.ts │ │ ├── Collapse.stories.tsx │ │ ├── Collapse.tsx │ │ └── index.ts │ │ ├── Ellipsis │ │ ├── Ellipsis.css.ts │ │ ├── Ellipsis.tsx │ │ └── index.ts │ │ ├── Portal │ │ ├── Portal.stories.tsx │ │ ├── Portal.tsx │ │ └── index.ts │ │ └── Transparent │ │ ├── Transparent.css.ts │ │ ├── Transparent.tsx │ │ └── index.ts ├── constants.ts ├── context │ ├── AnchorScrollContext.tsx │ ├── HistoryContext.tsx │ └── WorkerContext.ts ├── detector-wrapper │ ├── index.ts │ └── util.ts ├── global.css ├── hooks │ ├── useHistory.ts │ ├── useKey.ts │ ├── useMedia.ts │ ├── useMergeRefs.ts │ └── usePrevious.ts ├── index.tsx ├── mocks.ts ├── states │ ├── entity.ts │ ├── notification.ts │ ├── sidebar.ts │ └── worker.ts ├── styles │ └── variables.css.ts ├── supports.ts ├── types │ ├── event.ts │ ├── reg.ts │ └── store.ts ├── utils │ ├── __tests__ │ │ └── transformer.test.ts │ ├── focus.ts │ ├── selector.ts │ ├── transformer.ts │ └── types.ts ├── worker-client.ts └── worker-main.ts ├── tsconfig.json ├── types ├── .gitkeep └── resize-observer.d.ts ├── vite.config.source.js ├── vite.config.worker.js └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## Reproduced step 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | ## Expected behavior 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | ## Actual behavior 27 | 28 | A clear and concise description of what you actual to happen. 29 | 30 | ## Screenshots 31 | 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | ## Desktop (please complete the following information) 35 | 36 | - OS: [e.g. iOS] 37 | - Browser [e.g. chrome, safari] 38 | - Version [e.g. 22] 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## Is your feature request related to a problem? Please describe. 10 | 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Describe the solution you'd like 14 | 15 | A clear and concise description of what you want to happen. 16 | 17 | ## Describe alternatives you've considered 18 | 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | ## Additional context 22 | 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What does this change? 2 | 3 | A clear and concise description of what the changes is. 4 | 5 | ## References 6 | 7 | - If you have links to other resources, please list them here. (e.g. issue url, related pull request url, documents) 8 | 9 | ## Screenshots 10 | 11 | If applicable, add screenshots to help explain your changes. 12 | 13 | ## What can I check for bug fixes? 14 | 15 | Please briefly describe how you can confirm the resolution of the bug. 16 | -------------------------------------------------------------------------------- /.github/actions/setup-node/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Node 2 | 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - uses: actions/setup-node@v4 7 | with: 8 | node-version-file: '.node-version' 9 | cache: 'yarn' 10 | 11 | - shell: bash 12 | run: yarn --frozen-lockfile --check-files 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize] 8 | 9 | jobs: 10 | setup: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: ./.github/actions/setup-node 15 | 16 | test: 17 | runs-on: ubuntu-latest 18 | needs: [setup] 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: ./.github/actions/setup-node 22 | - run: yarn test 23 | 24 | lint: 25 | runs-on: ubuntu-latest 26 | needs: [setup] 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: ./.github/actions/setup-node 30 | - run: yarn lint 31 | 32 | typecheck: 33 | runs-on: ubuntu-latest 34 | needs: [setup] 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: ./.github/actions/setup-node 38 | - run: yarn typecheck 39 | 40 | build: 41 | runs-on: ubuntu-latest 42 | needs: [setup] 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: ./.github/actions/setup-node 46 | - run: yarn build 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | public/cv-wasm_* 3 | public/worker-dev.js 4 | 5 | # Created by https://www.gitignore.io/api/osx,windows,node 6 | # Edit at https://www.gitignore.io/?templates=osx,windows,node 7 | 8 | ### Node ### 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | 68 | # parcel-bundler cache (https://parceljs.org/) 69 | .cache 70 | 71 | # next.js build output 72 | .next 73 | 74 | # nuxt.js build output 75 | .nuxt 76 | 77 | # vuepress build output 78 | .vuepress/dist 79 | 80 | # Serverless directories 81 | .serverless 82 | 83 | # FuseBox cache 84 | .fusebox/ 85 | 86 | ### OSX ### 87 | # General 88 | .DS_Store 89 | .AppleDouble 90 | .LSOverride 91 | 92 | # Icon must end with two \r 93 | Icon 94 | 95 | # Thumbnails 96 | ._* 97 | 98 | # Files that might appear in the root of a volume 99 | .DocumentRevisions-V100 100 | .fseventsd 101 | .Spotlight-V100 102 | .TemporaryItems 103 | .Trashes 104 | .VolumeIcon.icns 105 | .com.apple.timemachine.donotpresent 106 | 107 | # Directories potentially created on remote AFP share 108 | .AppleDB 109 | .AppleDesktop 110 | Network Trash Folder 111 | Temporary Items 112 | .apdisk 113 | 114 | ### Windows ### 115 | # Windows thumbnail cache files 116 | Thumbs.db 117 | ehthumbs.db 118 | ehthumbs_vista.db 119 | 120 | # Dump file 121 | *.stackdump 122 | 123 | # Folder config file 124 | [Dd]esktop.ini 125 | 126 | # Recycle Bin used on file shares 127 | $RECYCLE.BIN/ 128 | 129 | # Windows Installer files 130 | *.cab 131 | *.msi 132 | *.msix 133 | *.msm 134 | *.msp 135 | 136 | # Windows shortcuts 137 | *.lnk 138 | 139 | # End of https://www.gitignore.io/api/osx,windows,node 140 | 141 | *storybook.log 142 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v18.19.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.scaffdog/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | files: ['./*'], 3 | }; 4 | -------------------------------------------------------------------------------- /.scaffdog/ui-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'ui-component' 3 | root: 'src/components' 4 | output: '**/*' 5 | ignore: 6 | - 'src/components/icons' 7 | - '**/{A..Z}*' 8 | - '**/__tests__' 9 | questions: 10 | name: 'Please enter a component name.' 11 | --- 12 | 13 | # Variables 14 | 15 | - name: `{{ inputs.name | pascal }}` 16 | 17 | # `{{ name }}/index.ts` 18 | 19 | ```typescript 20 | export * from './{{ name }}'; 21 | ``` 22 | 23 | # `{{ name }}/{{ name }}.tsx` 24 | 25 | ```typescript 26 | import React from 'react'; 27 | import * as styles from './{{ name }}.css'; 28 | 29 | export type Props = React.PropsWithChildren<{}>; 30 | 31 | export const {{ name }} = ({ children, ...rest }: Props) => { 32 | return ( 33 |
97 | No items found that match the text entered.
98 |
99 | Try filtering with different keywords :)
100 |
{children}
9 |21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean vel 22 | tristique risus. Aenean efficitur condimentum auctor. Mauris 23 | consectetur magna neque, sollicitudin viverra lorem semper eget. Lorem 24 | ipsum dolor sit amet, consectetur adipiscing elit. Nulla rhoncus 25 | convallis ante, ac interdum urna ultricies at. Nulla facilisi. Sed id 26 | turpis mi. Nulla semper imperdiet suscipit. Mauris quis malesuada 27 | risus, a efficitur justo. 28 |
29 |30 | Mauris accumsan nunc vel purus convallis luctus. Donec bibendum nulla 31 | lacus, vitae accumsan justo accumsan a. Duis ut nisi posuere, 32 | scelerisque sem nec, sagittis arcu. Sed vulputate imperdiet maximus. 33 | Praesent felis libero, consectetur non odio ac, ornare elementum 34 | lorem. Integer malesuada odio at efficitur volutpat. Sed sed volutpat 35 | ipsum. Sed eget lectus vitae risus sodales gravida vel vitae ante. 36 | Praesent semper nulla non elit mattis consectetur. Nullam pulvinar, 37 | neque vehicula malesuada ornare, enim orci posuere urna, a rutrum urna 38 | lorem eget lorem. Cras posuere faucibus turpis in fringilla. Etiam 39 | iaculis dolor ex. Morbi sollicitudin, purus vel vehicula dignissim, 40 | lectus urna euismod orci, sed porta tortor erat non ligula. Aenean ex 41 | risus, tempus facilisis sollicitudin eu, porta vitae justo. Nam 42 | tincidunt felis arcu, consectetur efficitur sapien euismod quis. 43 | Integer consequat erat id nibh maximus malesuada. 44 |
45 |;
70 | };
71 | }[keyof WorkerEventDataPayloadMap];
72 |
--------------------------------------------------------------------------------
/src/types/reg.ts:
--------------------------------------------------------------------------------
1 | export type RegVariant = 'passed' | 'new' | 'changed' | 'deleted';
2 |
3 | export type XIMGDiffConfig = {
4 | enabled: boolean;
5 | workerUrl?: string;
6 | };
7 |
8 | export type RegItem = {
9 | raw: string;
10 | encoded: string;
11 | };
12 |
13 | export type RegLink = {
14 | href: string;
15 | label: string;
16 | };
17 |
18 | export type RegData = {
19 | type: 'success' | 'danger';
20 | actualDir: string;
21 | expectedDir: string;
22 | diffDir: string;
23 | hasNew: boolean;
24 | hadPassed: boolean;
25 | hasFailed: boolean;
26 | hasDeleted: boolean;
27 | newItems: RegItem[];
28 | passedItems: RegItem[];
29 | failedItems: RegItem[];
30 | deletedItems: RegItem[];
31 | ximgdiffConfig?: XIMGDiffConfig;
32 | links?: RegLink[];
33 | diffImageExtension?: 'webp' | 'png';
34 | };
35 |
36 | export type RegEntity = {
37 | id: string;
38 | variant: RegVariant;
39 | name: string;
40 | diff: string;
41 | before: string;
42 | after: string;
43 | };
44 |
45 | export type RegStructualItem = {
46 | id: string;
47 | path: string;
48 | name: string;
49 | children: RegStructualItem[];
50 | };
51 |
52 | export type Rect = {
53 | width: number;
54 | height: number;
55 | x: number;
56 | y: number;
57 | };
58 |
59 | export type Size = {
60 | width: number;
61 | height: number;
62 | };
63 |
64 | export type DetectMatch = {
65 | bounding: Rect;
66 | center: Rect;
67 | diffMarkers: Rect[];
68 | };
69 |
70 | export type Matching = {
71 | images: Size[];
72 | matches: DetectMatch[][];
73 | strayingRects: Rect[][];
74 | };
75 |
--------------------------------------------------------------------------------
/src/types/store.ts:
--------------------------------------------------------------------------------
1 | import type { createStore } from 'jotai';
2 |
3 | export type Store = ReturnType