├── .commitlintrc.json ├── .czrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ ├── build_test.yml │ └── build_test_release.yml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── prepare-commit-msg ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── apps ├── docs │ ├── .eslintrc.json │ ├── components │ │ └── card │ │ │ ├── index.tsx │ │ │ └── style.module.css │ ├── index.d.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── pages │ │ ├── _app.tsx │ │ ├── _meta.json │ │ ├── about.mdx │ │ ├── docs │ │ │ ├── _meta.json │ │ │ ├── tutorial.mdx │ │ │ └── tutorial │ │ │ │ ├── _meta.json │ │ │ │ ├── advanced │ │ │ │ ├── _meta.json │ │ │ │ └── current-change.mdx │ │ │ │ ├── basic │ │ │ │ ├── action.mdx │ │ │ │ ├── hybrid.mdx │ │ │ │ └── state.mdx │ │ │ │ ├── getting-started.mdx │ │ │ │ ├── registry.mdx │ │ │ │ ├── trrack.mdx │ │ │ │ └── typescript.mdx │ │ ├── index.mdx │ │ ├── index.module.css │ │ ├── showcase.mdx │ │ ├── showcase.module.css │ │ └── typings.d.ts │ ├── postcss.config.js │ ├── project.json │ ├── public │ │ ├── .gitkeep │ │ └── assets │ │ │ ├── trrack_architecture.png │ │ │ ├── trrack_overview.png │ │ │ └── upset2.png │ ├── styles │ │ └── globals.css │ ├── tailwind.config.js │ ├── theme.config.jsx │ └── tsconfig.json ├── dummy-testing-library │ ├── .babelrc │ ├── .browserslistrc │ ├── .eslintrc.json │ ├── project.json │ ├── src │ │ ├── app │ │ │ ├── App.module.css │ │ │ ├── App.tsx │ │ │ ├── components │ │ │ │ └── graphRender.tsx │ │ │ └── utils │ │ │ │ └── translate.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.tsx │ │ ├── polyfills.ts │ │ └── styles.css │ ├── tsconfig.app.json │ └── tsconfig.json ├── react-trrack-example │ ├── .babelrc │ ├── .browserslistrc │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── app │ │ │ ├── App.tsx │ │ │ ├── components │ │ │ │ └── Navbar.tsx │ │ │ └── store │ │ │ │ ├── trrack.ts │ │ │ │ └── types.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.tsx │ │ ├── polyfills.ts │ │ └── styles.css │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── rtk-trrack-example │ ├── .babelrc │ ├── .browserslistrc │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── app │ │ │ ├── App.tsx │ │ │ ├── components │ │ │ │ └── Navbar.tsx │ │ │ ├── features │ │ │ │ ├── counter │ │ │ │ │ ├── counterSlice.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── posts │ │ │ │ │ └── postSlice.ts │ │ │ │ └── todo │ │ │ │ │ ├── taskSlice.ts │ │ │ │ │ └── types.ts │ │ │ └── store │ │ │ │ └── store.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.tsx │ │ ├── polyfills.ts │ │ └── styles.css │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json └── trrack-lineup-example │ ├── .babelrc │ ├── .eslintrc.json │ ├── browserslist │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── app │ │ ├── app.element.css │ │ ├── app.element.ts │ │ ├── lineup-manager.ts │ │ └── lineup-setup.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── babel.config.json ├── commitlint.config.js ├── index.html ├── jest.config.ts ├── jest.preset.js ├── lint-staged.config.js ├── nx.json ├── package.json ├── packages ├── core │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc.json │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── project.json │ ├── release.config.js │ ├── src │ │ ├── event │ │ │ └── index.ts │ │ ├── graph │ │ │ ├── components │ │ │ │ ├── index.ts │ │ │ │ └── node.ts │ │ │ ├── graph-slice.ts │ │ │ ├── index.ts │ │ │ └── provenance-graph.ts │ │ ├── index.ts │ │ ├── provenance │ │ │ ├── index.ts │ │ │ ├── trrack-config-opts.ts │ │ │ ├── trrack-events.ts │ │ │ ├── trrack.ts │ │ │ └── types.ts │ │ ├── registry │ │ │ ├── action.ts │ │ │ ├── index.ts │ │ │ └── reg.ts │ │ └── utils │ │ │ ├── id.ts │ │ │ └── index.ts │ ├── tests │ │ ├── event.spec.ts │ │ ├── import_export.test.ts │ │ ├── metadata-artifact.spec.ts │ │ └── node.spec.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── vite.config.ts └── redux │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintrc.json │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── project.json │ ├── release.config.js │ ├── src │ ├── index.ts │ ├── slice │ │ ├── index.ts │ │ ├── trrackableSliceCreator.ts │ │ ├── types.ts │ │ └── utils.ts │ └── store │ │ ├── index.ts │ │ ├── trrackStore.ts │ │ ├── trrackableStoreCreator.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── tests │ └── index.spec.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── vite.config.ts ├── release.config.base.js ├── tools ├── generators │ └── .gitkeep ├── scripts │ └── publish.mjs └── tsconfig.tools.json ├── tsconfig.base.json ├── vercel.json └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": {} 4 | } 5 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "@commitlint/cz-commitlint" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/ 3 | **/*/node_modules/**/* 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "globals": { 6 | "vi": true 7 | }, 8 | "overrides": [ 9 | { 10 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 11 | "rules": { 12 | "@nrwl/nx/enforce-module-boundaries": [ 13 | "error", 14 | { 15 | "enforceBuildableLibDependency": true, 16 | "allow": [], 17 | "depConstraints": [ 18 | { 19 | "sourceTag": "*", 20 | "onlyDependOnLibsWithTags": ["*"] 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | }, 27 | { 28 | "files": ["*.ts", "*.tsx"], 29 | "extends": ["plugin:@nrwl/nx/typescript"], 30 | "rules": {} 31 | }, 32 | { 33 | "files": ["*.js", "*.jsx"], 34 | "extends": ["plugin:@nrwl/nx/javascript"], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: Build -> Test 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - main 7 | - next 8 | - alpha 9 | - beta 10 | - '*.x*' 11 | pull_request: 12 | branches: 13 | - main 14 | - next 15 | - alpha 16 | - beta 17 | - '*.x*' 18 | 19 | jobs: 20 | test: 21 | name: Test -> Build 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Track main 31 | run: git branch --track main origin/main || echo "Already done" 32 | 33 | - name: 'Derive appropriate SHAs for base and head for `nx affected` commands' 34 | uses: nrwl/nx-set-shas@v3 35 | 36 | - run: | 37 | echo "BASE: ${{ env.NX_BASE }}" 38 | echo "HEAD: ${{ env.NX_HEAD }}" 39 | 40 | - name: Setup LTS Node 41 | uses: actions/setup-node@v3 42 | with: 43 | node-version: 'lts/*' 44 | cache: 'yarn' 45 | 46 | - name: Install yarn 47 | run: npm install -g yarn 48 | 49 | - name: Get yarn cache directory path 50 | id: yarn-cache-dir-path 51 | run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT 52 | 53 | - name: Cache yarn dependencies 54 | uses: actions/cache@v3 55 | id: yarn-cache 56 | with: 57 | path: | 58 | ${{ steps.yarn-cache-dir-path.outputs.dir }} 59 | **\node_modules 60 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 61 | restore-keys: | 62 | ${{ runner.os }}-yarn- 63 | 64 | - name: Install dependencies 65 | if: steps.yarn-cache.outputs.cache-hit != 'true' 66 | run: yarn install 67 | 68 | - name: Build 69 | run: | 70 | RUN=CI yarn run build-affected-libs 71 | 72 | - name: Test 73 | run: | 74 | RUN=CI yarn run test-affected-libs 75 | -------------------------------------------------------------------------------- /.github/workflows/build_test_release.yml: -------------------------------------------------------------------------------- 1 | name: Build -> Test -> Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | - alpha 9 | - beta 10 | - '*.x*' 11 | 12 | env: 13 | HUSKY: 0 14 | GIT_AUTHOR_NAME: kirangadhave 15 | GIT_AUTHOR_EMAIL: kirangadhave2@gmail.com 16 | GIT_COMMITTER_NAME: kirangadhave 17 | GIT_COMMITTER_EMAIL: kirangadhave2@gmail.com 18 | 19 | jobs: 20 | release: 21 | name: Build -> Test -> Release 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | with: 28 | fetch-depth: 0 29 | token: ${{ secrets.TRRACK_GITHUB_TOKEN }} 30 | 31 | - name: Track main 32 | run: git branch --track main origin/main || echo "Already done" 33 | 34 | - name: 'Derive appropriate SHAs for base and head for `nx affected` commands' 35 | uses: nrwl/nx-set-shas@v3 36 | 37 | - run: | 38 | echo "BASE: ${{ env.NX_BASE }}" 39 | echo "HEAD: ${{ env.NX_HEAD }}" 40 | 41 | - name: Setup LTS Node 42 | uses: actions/setup-node@v3 43 | with: 44 | node-version: 'lts/*' 45 | cache: 'yarn' 46 | 47 | - name: Install yarn 48 | run: npm install -g yarn 49 | 50 | - name: Get yarn cache directory path 51 | id: yarn-cache-dir-path 52 | run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT 53 | 54 | - name: Cache yarn dependencies 55 | uses: actions/cache@v3 56 | id: yarn-cache 57 | with: 58 | path: | 59 | ${{ steps.yarn-cache-dir-path.outputs.dir }} 60 | **\node_modules 61 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 62 | restore-keys: | 63 | ${{ runner.os }}-yarn- 64 | 65 | - name: Install dependencies 66 | if: steps.yarn-cache.outputs.cache-hit != 'true' 67 | run: yarn install 68 | 69 | - name: Build 70 | run: | 71 | RUN=CI yarn run build-affected-libs 72 | 73 | - name: Test 74 | run: | 75 | RUN=CI yarn run test-affected-libs 76 | 77 | - name: Release 78 | if: ${{ success() && (github.event_name != 'pull_request' || github.event.action == 'closed' && github.event.pull_request.merged == true) }} 79 | env: 80 | GITHUB_TOKEN: ${{ secrets.TRRACK_GITHUB_TOKEN }} 81 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 82 | run: | 83 | RUN=CI yarn run release 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # Next.js 42 | .next 43 | 44 | # Nx 45 | migrations.json 46 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit "$1" || (echo "Commit using 'yarn commit' command"; exit 1;) 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged --relative 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | 5 | exec < /dev/tty && npx cz --hook || true < /dev/null 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.17.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "conventionalCommits.scopes": ["core"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, The Trrack Team 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trrack 2 | 3 | Trrack stands for **r**eproducible **track**ing. Originally Trrack is a web-based provenance tracking library that can track application state in directed provenance graph. 4 | 5 | This branch implements action based provenance tracking. 6 | 7 | [![license](https://img.shields.io/github/license/trrack/trrackjs?style=plastic)](https://github.com/Trrack/trrackjs/blob/main/LICENSE) 8 | [![npm latest version](https://img.shields.io/npm/v/@trrack/core?style=plastic)](https://www.npmjs.com/package/@trrack/core) 9 | [![npm downloads](https://img.shields.io/npm/dt/@trrack/core?style=plastic)](https://www.npmjs.com/package/@trrack/core) 10 | [![Github Action](https://img.shields.io/github/actions/workflow/status/trrack/trrackjs/release.yml?branch=main)](https://github.com/Trrack/trrackjs/actions/workflows/release.yml?query=branch%3Amain) 11 | 12 | 13 | ## Development 14 | 15 | Clone the repository and switch to `trrack-action` branch. 16 | This project was generated using [Nx](https://nx.dev). Please refer to [Nx](https://nx.dev) for more any questions about monorepo setup. 17 | 18 | ### Basic 19 | Serve the react example by running the following: 20 | 21 | ```bash 22 | npx nx react-trrack-example:serve 23 | ``` 24 | Any changes made to the `core` package will cause updates to the react example for easier testing 25 | 26 | ### Advanced 27 | To get started with development run: 28 | 29 | ```bash 30 | yarn install # Trrack can also work with npm, but it uses workspaces feature which we have only tested with yarn. 31 | 32 | yarn dev:all # Will run all examples 33 | 34 | yarn test:all:watch # Will run tests for all trrack pacakges in watch mode 35 | ``` 36 | 37 | The repository is structured as follows: 38 | 39 | ```bash 40 | trrack-monorepo 41 | | 42 | |--- pacakges # trrack library is located in this folder 43 | |--- core # Core action-based tracking library 44 | |--- redux # Redux toolkit wrapper for core 45 | | 46 | |--- apps # Trrack examples are located in this folder 47 | |--- react-trrack-example 48 | |--- rtk-trrack-example 49 | ``` 50 | 51 | Following the standards for Nx monorepos please install any dependency for the applications directly to root workspace. This ensures all the applications use same versions of any dependency. For the packages, install the dependency to appropriate package. 52 | -------------------------------------------------------------------------------- /apps/docs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nrwl/nx/react-typescript", 4 | "next", 5 | "next/core-web-vitals", 6 | "../../.eslintrc.json" 7 | ], 8 | "ignorePatterns": ["!**/*", ".next/**/*"], 9 | "overrides": [ 10 | { 11 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 12 | "rules": { 13 | "@next/next/no-html-link-for-pages": ["error", "apps/docs/pages"] 14 | } 15 | }, 16 | { 17 | "files": ["*.ts", "*.tsx"], 18 | "rules": {} 19 | }, 20 | { 21 | "files": ["*.js", "*.jsx"], 22 | "rules": {} 23 | } 24 | ], 25 | "rules": { 26 | "@next/next/no-html-link-for-pages": "off" 27 | }, 28 | "env": { 29 | "jest": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/docs/components/card/index.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'clsx'; 2 | import Link from 'next/link'; 3 | import React from 'react'; 4 | 5 | import styles from './style.module.css'; 6 | 7 | export function Card({ 8 | children, 9 | title, 10 | icon, 11 | image, 12 | arrow, 13 | demo, 14 | href, 15 | ...props 16 | }) { 17 | const animatedArrow = arrow ? ( 18 | 24 | → 25 | 26 | ) : null; 27 | 28 | if (image) { 29 | return ( 30 | 40 | {children} 41 | 48 | {icon} 49 | 50 | {title} 51 | {animatedArrow} 52 | 53 | 54 | 55 | ); 56 | } 57 | 58 | return ( 59 | 69 | 76 | {icon} 77 | {title} 78 | {animatedArrow} 79 | 80 | 81 | ); 82 | } 83 | 84 | export function Cards({ children, num, ...props }) { 85 | return ( 86 |
96 | {children} 97 |
98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /apps/docs/components/card/style.module.css: -------------------------------------------------------------------------------- 1 | .cards { 2 | display: grid; 3 | --rows: 3; 4 | grid-template-columns: repeat( 5 | auto-fill, 6 | minmax(max(250px, calc((100% - 1rem * 2) / var(--rows))), 1fr) 7 | ); 8 | } 9 | 10 | .card img { 11 | -webkit-user-drag: none; 12 | } 13 | 14 | .card:hover svg { 15 | color: currentColor; 16 | } 17 | 18 | .card svg { 19 | width: 1.5rem; 20 | color: #00000033; 21 | transition: color 0.3s ease; 22 | } 23 | 24 | .card p { 25 | margin-top: 0.5rem; 26 | } 27 | 28 | .card .title { 29 | display: flex; 30 | font-weight: 600; 31 | align-items: flex-start; 32 | } 33 | 34 | :global(.dark) .card svg { 35 | color: #ffffff66; 36 | } 37 | :global(.dark) .card:hover svg { 38 | color: currentColor; 39 | } 40 | -------------------------------------------------------------------------------- /apps/docs/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.svg' { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /apps/docs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/docs/next.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | // const { withNx } = require('@nrwl/next/plugins/with-nx'); 5 | 6 | // /** 7 | // * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} 8 | // **/ 9 | // const nextConfig = { 10 | // nx: { 11 | // // Set this to true if you would like to to use SVGR 12 | // // See: https://github.com/gregberge/svgr 13 | // svgr: false, 14 | // }, 15 | // }; 16 | 17 | // module.exports = withNx(nextConfig); 18 | 19 | const withNextra = require('nextra')({ 20 | theme: 'nextra-theme-docs', 21 | themeConfig: './theme.config.jsx', 22 | }); 23 | 24 | module.exports = withNextra({ 25 | basePath: '/trrack', 26 | }); 27 | 28 | // If you have other Next.js configurations, you can pass them as the parameter: 29 | // module.exports = withNextra({ /* other next.js config */ }) 30 | -------------------------------------------------------------------------------- /apps/docs/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | 3 | import React from 'react'; 4 | 5 | import type { ReactElement } from 'react'; 6 | import type { AppProps } from 'next/app'; 7 | 8 | export default function Nextra({ 9 | Component, 10 | pageProps, 11 | }: AppProps): ReactElement { 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /apps/docs/pages/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "title": "Trrack", 4 | "type": "page", 5 | "display": "hidden", 6 | "theme": { 7 | "layout": "full" 8 | } 9 | }, 10 | "docs": { 11 | "title": "Documentation", 12 | "type": "page" 13 | }, 14 | "showcase": { 15 | "title": "Showcase", 16 | "type": "page", 17 | "theme": { 18 | "typesetting": "article", 19 | "layout": "full" 20 | } 21 | }, 22 | "about": { 23 | "title": "About", 24 | "type": "page", 25 | "theme": { 26 | "typesetting": "article" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/docs/pages/about.mdx: -------------------------------------------------------------------------------- 1 | ## Team 2 | 3 | Currently the project is maintained by [Kiran Gadhave](https://www.kirangadhave.me/), [Zach Cutler](https://vdl.sci.utah.edu/team/zcutler/) and [Dr. Alexander Lex](https://vdl.sci.utah.edu/team/lex/). 4 | 5 | ## Publication 6 | 7 | Check our [the paper](https://vdl.sci.utah.edu/publications/2020_visshort_trrack/) to learn about the design philosophy. 8 | 9 | If you're using Trrack in an academic project, please cite: 10 | 11 | ```text copy 12 | Z. Cutler, K. Gadhave and A. Lex, "Trrack: A Library for Provenance-Tracking in Web-Based Visualizations," 2020 IEEE Visualization Conference (VIS), Salt Lake City, UT, USA, 2020, pp. 116-120, doi: 10.1109/VIS47514.2020.00030. 13 | ``` 14 | 15 | ### BibTeX: 16 | 17 | ```bibtex copy 18 | @INPROCEEDINGS{9331264, 19 | author={Cutler, Zach and Gadhave, Kiran and Lex, Alexander}, 20 | booktitle={2020 IEEE Visualization Conference (VIS)}, 21 | title={Trrack: A Library for Provenance-Tracking in Web-Based Visualizations}, 22 | year={2020}, 23 | volume={}, 24 | number={}, 25 | pages={116-120}, 26 | abstract={Provenance-tracking is widely acknowledged as an important feature of visualization systems. By tracking provenance data, visualization designers can provide a wide variety of functionality, ranging from action recovery (undo/redo), reproducibility, collaboration and sharing, to logging in support of quantitative and longitudinal evaluation. However, no widely used library that can provide that functionality is current available. As a consequence, visualization designers either develop ad hoc solutions that are rarely comprehensive, or do not track provenance at all. In this paper, we introduce a web-based software library - Trrack - that is designed for easy integration in existing or future visualization systems. Trrack supports a wide range of use cases, from simple action recovery, to capturing intent and reasoning, and can be used to share states with collaborators and store provenance on a server. Trrack also includes an optional provenance visualization component that supports annotation of states and aggregation of events.}, 27 | keywords={}, 28 | doi={10.1109/VIS47514.2020.00030}, 29 | ISSN={}, 30 | month={Oct},} 31 | 32 | ``` 33 | 34 | ## License 35 | 36 | Trrack is licensed under the BSD3 license. 37 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "tutorial": "Tutorial" 3 | } 4 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial.mdx: -------------------------------------------------------------------------------- 1 | # Tutorials 2 | 3 | import { Card, Cards } from '../../components/card'; 4 | 5 | 6 | 14 | 19 | 20 | } 21 | title="Getting started" 22 | href="/docs/tutorial/getting-started" 23 | /> 24 | 32 | 37 | 38 | } 39 | title="Action Registry" 40 | href="/docs/tutorial/registry" 41 | /> 42 | 43 | 51 | 56 | 57 | } 58 | title="Basic Usage" 59 | href="/docs/tutorial/basic/state" 60 | /> 61 | 71 | 76 | 77 | } 78 | title="TypeScript" 79 | href="/docs/tutorial/typescript" 80 | /> 81 | {/* 91 | 96 | 97 | } 98 | title="Advanced" 99 | href="/docs/tutorial/advanced" 100 | /> */} 101 | 102 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "getting-started": "Getting Started", 3 | "registry": "Action Registry", 4 | "trrack": "Trrack", 5 | "basic": "Basic Usage", 6 | "advanced": "Advanced Usage", 7 | "typescript": "TypeScript" 8 | } 9 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/advanced/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "current-change": "Current Change Event" 3 | } 4 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/advanced/current-change.mdx: -------------------------------------------------------------------------------- 1 | # Current Change Event 2 | 3 | ### Basic usage 4 | 5 | By default Trrack keeps a pointer the latest added node. This can be accessed using `trrack.current`. Using `undo`, `redo`, and `todo` will update this pointer to the appropriate node. Trrack emits a `currentChange` event whenever current node changes due to any reason. 6 | 7 | To respond to the `currentChange` event you can register a listener. The `currentChange` listener gets an optional argument which is the `trigger` for the current change. 8 | 9 | ```ts 10 | import { initializeTrrack } from '@trrack/core'; 11 | 12 | const trrack = initializeTrrack({ ... }); 13 | 14 | trrack.currentChange(() => { 15 | // Do something... 16 | }); 17 | ``` 18 | 19 | ### Cleaning up 20 | 21 | `currentChange` function returns an unsubscribe function which can be used to remove the listener to avoid memory leaks. 22 | 23 | ```ts 24 | import { initializeTrrack } from '@trrack/core'; 25 | 26 | const trrack = initializeTrrack({ ... }); 27 | 28 | const unsubscribe = trrack.currentChange(() => { 29 | // Do something... 30 | }); 31 | 32 | // Call unsubscribe to remove the listener 33 | unsubscribe(); 34 | ``` 35 | 36 | ### Advanced usage with state management libraries 37 | 38 | Current change event is the primary way to interact with the store from a state management library like `redux` or `pinia`. 39 | 40 | `currentChange` event is used to sync the state of the store with current state from Trrack. 41 | A general workflow for such integration looks like following: 42 | 43 | - Initialize store 44 | - Initialize trrack with initialState (possibly from store) 45 | - Setup store update actions to also inform Trrack about the udpates 46 | - Register `currentChange` listener to update the store state with the current state from Trrack in case of `traversal`. 47 | 48 | Let's assume we have a provenance graph which looks as follows: 49 | 50 | ```ts 51 | | 52 | A (State 1) 53 | | 54 | B (State 2) 55 | | 56 | C (State 3) <-- Current 57 | | 58 | ``` 59 | 60 | The state of the store is the same as `State 3`. 61 | 62 | If we execute `trrack.undo()` at this point, the graph will be updated: 63 | 64 | ```ts 65 | | 66 | A (State 1) 67 | | 68 | B (State 2) <-- Current 69 | | 70 | C (State 3) 71 | | 72 | ``` 73 | 74 | However, our store state is still `State 3`. Inside the `currentChange` listener we can update the store state with the current state from Trrack. 75 | 76 | ```ts 77 | trrack.currentChange(() => { 78 | const newState = trrack.getState(); // gets the state from current node 79 | 80 | store.update(newState); // update the store state 81 | }); 82 | ``` 83 | 84 | This works great when we are traversing the graph. However, if we execute `trrack.apply()` when `current` is pointing to `State 3` the store updates to `State 4`. Our state update actions also inform trrack about the new state, and the graph will be updated: 85 | 86 | ```ts 87 | | 88 | A (State 1) 89 | | 90 | B (State 2) 91 | | 92 | C (State 3) 93 | | 94 | D (State 4) <-- Current 95 | | 96 | ``` 97 | 98 | Since a new node was added to Trrack and current pointer changed to point to it, the `currentChange` event is triggered, which updates the store, and may cause re-rendering. 99 | 100 | Trrack passes `trigger` argument to the `currentChange` listener. If `trigger` is `new` we can skip the store update. The value for `trigger` can be `traversal` or `new`. `traversal` means that the current node was changed due to a `undo`, `redo`, or `to` command. `new` means that the current node was changed due to a `apply` command. This way we can avoid updating the store state when the current node is changed due to addition of new node. 101 | 102 | ```ts 103 | trrack.currentChange((trigger: Trigger) => { 104 | if (trigger === 'new') return; // skip store update 105 | 106 | const newState = trrack.getState(); // gets the state from current node 107 | store.update(newState); // update the store state 108 | }); 109 | ``` 110 | 111 | Such a use case can be found very frequently when two state containers update each other. To handle such case, the `currentChange` function accepts a second optional boolean argument which is `skipOnNew`. 112 | Passing `true` to `skipOnNew` will skip the store update when the current node is changed due to addition of new node. 113 | 114 | ```ts 115 | trrack.currentChange((trigger: Trigger) => { 116 | /** do not need this anymore */ 117 | // if (trigger === 'new') return; 118 | 119 | const newState = trrack.getState(); // gets the state from current node 120 | store.update(newState); // update the store state 121 | }, true); // skip store update when current node is changed due to addition of new node 122 | ``` 123 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/basic/action.mdx: -------------------------------------------------------------------------------- 1 | # Action Tracking 2 | 3 | Here is a sandbox which shows pure action based provenance trracking. 4 | 5 | 18 | 19 | [![Edit Trrack Pure Action Tracking Example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/trrack-pure-action-tracking-example-495zv7?fontsize=14&hidenavigation=1&theme=dark) 20 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/basic/hybrid.mdx: -------------------------------------------------------------------------------- 1 | # Hybrid Tracking 2 | 3 | Here is a sandbox which shows hybrid provenance trracking. 4 | 5 | 18 | 19 | [![Edit Trrack Hybrid Example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/trrack-hybrid-example-iekj6z?fontsize=14&hidenavigation=1&theme=dark) 20 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/basic/state.mdx: -------------------------------------------------------------------------------- 1 | # State Tracking 2 | 3 | We will walk through a complete example which shows how to setup Trrack for pure state-based provenance tracking. 4 | 5 | We start by creating our initial state: 6 | 7 | ```ts 8 | const state = { 9 | counter: 0, 10 | }; 11 | ``` 12 | 13 | Then we initialize the registry and register an state-update action which increments the counter with a value: 14 | 15 | ```ts 16 | const registry = Registry.create(); 17 | 18 | const incrementAction = registry.register('increment', (state, val) => { 19 | state.counter += val; 20 | }); 21 | ``` 22 | 23 | We can use the `state` and `registry` to initialize Trrack as follows: 24 | 25 | ```ts 26 | const trrack = initializeTrrack({ 27 | initialState: state, 28 | registry, 29 | }); 30 | ``` 31 | 32 | We can trigger updates to state by using `incrementAction` as follows: 33 | 34 | ```ts 35 | trrack.apply('Increment by 1', incrementAction(1)); 36 | ``` 37 | 38 | Here is a sandbox which shows state based provenance trracking. 39 | 40 | 53 | 54 | [![Edit Trrack Pure State Tracking Example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/trrack-simple-example-em2hem?fontsize=14&hidenavigation=1&theme=dark) 55 | 56 | ``` 57 | 58 | ``` 59 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/getting-started.mdx: -------------------------------------------------------------------------------- 1 | import { Tabs, Tab } from 'nextra-theme-docs'; 2 | 3 | # Getting Started 4 | 5 | ## Default installation 6 | 7 | Run one of the following commands to add Trrack to your project: 8 | 9 | 10 | 11 | ```bash copy 12 | yarn add @trrack/core 13 | ``` 14 | 15 | 16 | ```bash copy 17 | npm install @trrack/core 18 | ``` 19 | 20 | 21 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/registry.mdx: -------------------------------------------------------------------------------- 1 | import { Callout } from 'nextra-theme-docs'; 2 | 3 | # Action Registry 4 | 5 | The first step in adding `Trrack` to your application is to create a action registry using the `Registry` class. 6 | 7 | ```javascript 8 | const registry = Registry.create(); 9 | ``` 10 | 11 | Now actions can be registered in the registry. Actions are the only way `Trrack` knows that something has happened and it has to update the provenance graph. 12 | 13 | ## Action Types 14 | 15 | There are two types of actions that can be registered. 16 | 17 | ### State Action 18 | 19 | State actions get the current `state` of the application as an argument and can modify it based on the `payload`, which is the second argument. 20 | This is similar to [redux reducers](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#reducers). 21 | 22 | ```typescript 23 | // Action which updates the `count` key in the state by desired value. 24 | const incrementAction = registry.register( 25 | 'increment-action', // Action name 26 | // Action handler which receives the current state and a payload. 27 | (currentState: State, payload: number) => { 28 | currentState.count += payload; 29 | } 30 | ); 31 | ``` 32 | 33 | When state actions are executed, the changes to the state are stored in a new provenance node. 34 | 35 | ### Trrack Action 36 | 37 | The other type of action that is supported is called a `Trrack action`. These actions do not modify the application state, but execute any arbitrary side effect. 38 | 39 | When `trrack action` is executed, `Trrack` keeps a record of execution and the payload of the action. 40 | You need to provide a `undo` counterpart to each action to allow `Trrack` to undo the action. 41 | 42 | An `trrack action` can be set as undo for itself. 43 | 44 | ```typescript 45 | const arr = [1, 2, 3, 4]; 46 | 47 | registry.register('sort', (sortBy: 'asc' | 'dsc') => { 48 | if (sortBy === 'asc') arr.sort(); 49 | if (sortBy === 'dsc') arr.sort().reverse(); 50 | 51 | return { 52 | undo: { 53 | type: 'sort', 54 | payload: sortBy === 'asc' ? 'dsc' : 'asc', 55 | meta: { 56 | hasSideEffects: true, 57 | }, 58 | }, 59 | }; 60 | }); 61 | ``` 62 | 63 | 64 | Trrack provides no guarantees that the `trrack action` is properly undone by the 65 | `undo action`. We rely on developers for correct implementation. Trrack can 66 | only guarantee that the undo action will be called properly during the 67 | traversal. 68 | 69 | 70 | 71 | You should be using `state actions` exclusively in your application. State 72 | actions allow for faster traversal through the provenance graph. 73 | 74 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/trrack.mdx: -------------------------------------------------------------------------------- 1 | # Trrack 2 | 3 | ## State 4 | 5 | After creating the `registry` and registering our actions we can initialize Trrack instance. 6 | 7 | Apart from the `registry`, we need the `application state` to initilize Trrack. 8 | 9 | ```typescript copy 10 | type State { 11 | // ... model your application state here. Flat structure is better for modular updates as well as storing diffs. 12 | } 13 | 14 | const state: State = { 15 | // ... initialize your application state here with default values 16 | } 17 | ``` 18 | 19 | ## Trrack Instance 20 | 21 | Now with the `registry` and `state` we can initialize Trrack. 22 | 23 | ```typescript copy 24 | import { initializeTrrack } from '@trrack/core'; 25 | 26 | const trrack = initializeTrrack({ 27 | initialState: state, 28 | registry, // registry created in the previous section 29 | }); 30 | ``` 31 | 32 | ## Listener 33 | 34 | Trrack provides a listener to subscribe to state change. State change happens when moving between nodes in the provenance graph either due to creation of a new node, an undo or a redo. 35 | 36 | ```typescript copy 37 | trrack.currentChange(() => { 38 | // Response to state change here 39 | }); 40 | ``` 41 | 42 | You can register multiple listeners and Trrack will call them all in order. 43 | 44 | ## Accessing current state 45 | 46 | Trrack stores the state as either as entire snapshot or a diff from the previous state. 47 | You can access the current state using the `getState` method as follows. 48 | 49 | ```typescript copy 50 | const state = trrack.getState(); 51 | ``` 52 | 53 | ## Traversal 54 | 55 | You can jump to any node in the provenance graph using the `to` method. 56 | 57 | ```typescript copy 58 | trrack.to(); 59 | ``` 60 | 61 | Trrack also provides helper methods to `undo` and `redo` in the provenance graph using methods of the same name. 62 | -------------------------------------------------------------------------------- /apps/docs/pages/docs/tutorial/typescript.mdx: -------------------------------------------------------------------------------- 1 | # TypeScript 2 | 3 | `@trrack/core` and `@trrack/redux` are implemented in typescript and ship with its own typings. 4 | -------------------------------------------------------------------------------- /apps/docs/pages/index.mdx: -------------------------------------------------------------------------------- 1 | import styles from './index.module.css'; 2 | import Link from 'next/link'; 3 | 4 |
5 |
6 |

7 | Enable interaction provenance
8 |

9 |

10 | with Trrack v2. 11 |

12 |

15 | 16 | Get started → 17 | 18 |

19 | 20 |
21 | 22 |
23 | 24 | ## The Provenance Tracking Library 25 | 26 | Trrack is a library to create and track provenance (history) in web-based apps. Trrack allows you to create and maintain a non-linear provenance graph representing the history of the state of your visualization. Through this graph, you can easily implement complete action recovery, as well as store custom metadata and annotations. 27 | 28 | ![Overview of applications implementing the trrack library, and the trrack provenance visualization](/assets/trrack_overview.png) 29 | 30 | ## Features 31 | 32 | - Power you application to track user interactions or changes 33 | - Enable undo/redo functionality 34 | {/* - Easy state sharing through a URL */} 35 | - Track changes in non-linear manner with branches 36 | - Add custom metadata and annotations to each node in the graph 37 | {/* - Built in Firebase support for storing large graphs */} 38 | - Simple API 39 | - Full Typescript support 40 | 41 | ## Companion Library 42 | 43 | Trrack does back-end history management only. If you want to use the history/provenance visualization as well, check out the [trrack-vis library](https://github.com/Trrack/trrackvis), which is designed to provide a customizable front-end for the Trrack library. 44 | 45 | ## Usage 46 | 47 | To use Trrack, your application has to be explicit about state: any action that you want to track has to be captured as part of a state that you pass to the Trrack library. 48 | 49 | ![Overview of how Trrack integrates with client software.](/assets/trrack_architecture.png) 50 | 51 | Trrack v2 also supports action-based provenance tracking. 52 | 53 | ## Funding 54 | 55 | We gratefully acknowledge funding by the National Science Foundation (IIS 1751238). 56 | 57 |
58 | 59 |
60 | 61 | 79 | 80 | 196 | -------------------------------------------------------------------------------- /apps/docs/pages/index.module.css: -------------------------------------------------------------------------------- 1 | .highlight-logo { 2 | color: #54c8ff; 3 | } 4 | 5 | .file { 6 | display: inline-block; 7 | font-size: 0.9em; 8 | padding: 1px 6px; 9 | border-radius: 0.375rem; 10 | border: 1px solid #0077ff; 11 | color: #0077ff; 12 | font-weight: 600; 13 | cursor: default; 14 | user-select: none; 15 | } 16 | .file.active { 17 | background-color: #0077ff; 18 | color: white; 19 | } 20 | 21 | .comparison { 22 | display: flex; 23 | justify-content: space-evenly; 24 | align-items: center; 25 | } 26 | .comparison > svg { 27 | opacity: 0.3; 28 | } 29 | 30 | .optimization { 31 | display: flex; 32 | justify-content: center; 33 | margin: 0; 34 | padding: 1.5rem 1rem; 35 | background: linear-gradient(27deg, #3d3d3d, #000000); 36 | color: #e3e3e3; 37 | border-radius: 0.375rem; 38 | font-weight: 300; 39 | } 40 | 41 | :global(.dark) .optimization { 42 | background: linear-gradient(27deg, #3d3d3d, #252525); 43 | } 44 | 45 | a.cta { 46 | display: inline-block; 47 | background: linear-gradient(to bottom, #238aff, #0077ff); 48 | text-decoration: none; 49 | border-radius: 9999px; 50 | color: white; 51 | padding: 0.75rem 1.5rem; 52 | margin-top: 0.5rem; 53 | text-shadow: 0 1px 1px #00387838; 54 | box-shadow: 0 1px 2px #00295738; 55 | transition: all 0.2s ease; 56 | -webkit-user-drag: none; 57 | user-select: none; 58 | -webkit-tap-highlight-color: transparent; 59 | } 60 | a.cta span { 61 | display: inline-block; 62 | transition: all 0.2s ease; 63 | } 64 | a.cta:hover span { 65 | transform: translateX(3px); 66 | } 67 | a.cta:hover { 68 | box-shadow: 0 5px 30px -10px #0078ffab; 69 | filter: brightness(1.05); 70 | } 71 | a.cta:active { 72 | box-shadow: 0 1px 3px #00295738; 73 | filter: brightness(0.95); 74 | } 75 | a.cta:active span { 76 | transform: translateX(5px); 77 | } 78 | a.cta:focus-visible { 79 | outline: 2px solid hsl(var(--nextra-primary-hue) 100% 77%); 80 | outline-offset: 2px; 81 | } 82 | a.cta:focus-visible span { 83 | transform: translateX(3px); 84 | } 85 | -------------------------------------------------------------------------------- /apps/docs/pages/showcase.mdx: -------------------------------------------------------------------------------- 1 | import { Card, Cards } from '../components/card'; 2 | import styles from './showcase.module.css'; 3 | 4 |

5 | Showcase 6 |

7 | 8 |

9 | Some project powered by Trrack 10 |

11 | 12 |
13 | 14 | 21 | <> 22 | ![](https://vdl.sci.utah.edu/assets/images/publications/2021_intent.png) 23 | 24 | 25 | 26 | 33 | <> 34 | ![](https://vdl.sci.utah.edu/assets/images/publications/2022_eurovis_reusing_intent.png) 35 | 36 | 37 | 44 | <> 45 | ![](/assets/upset2.png) 46 | 47 | 48 | 49 | 56 | <> 57 | ![](https://vdl.sci.utah.edu/assets/images/publications/2021_ivi_sanguine.png) 58 | 59 | 60 | 67 | 68 | 69 |
70 | -------------------------------------------------------------------------------- /apps/docs/pages/showcase.module.css: -------------------------------------------------------------------------------- 1 | .showcase img { 2 | aspect-ratio: 12/6.3; 3 | object-fit: cover; 4 | } 5 | -------------------------------------------------------------------------------- /apps/docs/pages/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.css'; 2 | -------------------------------------------------------------------------------- /apps/docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/docs/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/docs", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "nx:run-commands", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "root": "apps/docs", 13 | "outputPath": "dist/apps/docs", 14 | "command": "npx next build", 15 | "cwd": "apps/docs" 16 | }, 17 | "configurations": { 18 | "development": { 19 | "outputPath": "apps/docs" 20 | }, 21 | "production": {} 22 | } 23 | }, 24 | "serve": { 25 | "executor": "nx:run-commands", 26 | "defaultConfiguration": "development", 27 | "options": { 28 | "command": "npx next dev", 29 | "cwd": "apps/docs", 30 | "buildTarget": "docs:build", 31 | "dev": true 32 | }, 33 | "configurations": { 34 | "development": { 35 | "buildTarget": "docs:build:development", 36 | "dev": true 37 | }, 38 | "production": { 39 | "buildTarget": "docs:build:production", 40 | "dev": false 41 | } 42 | } 43 | }, 44 | "export": { 45 | "executor": "nx:run-commands", 46 | "options": { 47 | "buildTarget": "docs:build:production", 48 | "cwd": "apps/docs", 49 | "commands": ["npx next start", "npx next export"] 50 | } 51 | }, 52 | "lint": { 53 | "executor": "@nrwl/linter:eslint", 54 | "outputs": ["{options.outputFile}"], 55 | "options": { 56 | "lintFilePatterns": ["apps/docs/**/*.{ts,tsx,js,jsx}"] 57 | } 58 | } 59 | }, 60 | "tags": [] 61 | } 62 | -------------------------------------------------------------------------------- /apps/docs/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/docs/public/.gitkeep -------------------------------------------------------------------------------- /apps/docs/public/assets/trrack_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/docs/public/assets/trrack_architecture.png -------------------------------------------------------------------------------- /apps/docs/public/assets/trrack_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/docs/public/assets/trrack_overview.png -------------------------------------------------------------------------------- /apps/docs/public/assets/upset2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/docs/public/assets/upset2.png -------------------------------------------------------------------------------- /apps/docs/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './components/**/*.{js,ts,jsx,tsx}', 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | darkMode: 'class', 12 | }; 13 | -------------------------------------------------------------------------------- /apps/docs/theme.config.jsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | 3 | import styles from './pages/index.module.css'; 4 | 5 | const theme = { 6 | logo: ( 7 |

8 | trrack - A library for{' '} 9 | r 10 | eproducible tracking 11 |

12 | ), 13 | project: { 14 | link: 'https://github.com/Trrack/trrackjs', 15 | }, 16 | docsRepositoryBase: 'https://github.com/Trrack/trrackjs/tree/main/apps/docs', 17 | head: ( 18 | <> 19 | 20 | 24 | 25 | ), 26 | useNextSeoProps() { 27 | const { route } = useRouter(); 28 | if (route !== '/' && route !== '/docs') 29 | return { 30 | titleTemplate: '%s - Trrack', 31 | }; 32 | }, 33 | chat: { 34 | link: 'https://github.com/kirangadhave/', 35 | icon: 'Get in touch', 36 | }, 37 | banner: { 38 | key: '2.0-release', 39 | text: ( 40 | 45 | 🎉 This is documentation for Trrack 2.0.{' '} 46 | Click here for legacy Trrack documentation → 47 | 48 | ), 49 | }, 50 | footer: { 51 | text: ( 52 | 53 | BSD 3 {new Date().getFullYear()} ©{' '} 54 | 55 | The Trrack Team 56 | 57 | . 58 | 59 | ), 60 | }, 61 | }; 62 | 63 | export default theme; 64 | -------------------------------------------------------------------------------- /apps/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "baseUrl": ".", 6 | "allowJs": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": false, 10 | "forceConsistentCasingInFileNames": true, 11 | "noEmit": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "incremental": true 15 | }, 16 | "include": [ 17 | "src/**/*.ts", 18 | "src/**/*.tsx", 19 | "src/**/*.js", 20 | "src/**/*.jsx", 21 | "next-env.d.ts" 22 | ], 23 | "exclude": ["node_modules", "jest.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/react/babel", 5 | { 6 | "runtime": "automatic" 7 | } 8 | ] 9 | ], 10 | "plugins": [] 11 | } 12 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by: 2 | # 1. autoprefixer to adjust CSS to support the below specified browsers 3 | # 2. babel preset-env to adjust included polyfills 4 | # 5 | # For additional information regarding the format and rule options, please see: 6 | # https://github.com/browserslist/browserslist#queries 7 | # 8 | # If you need to support different browsers in production, you may tweak the list below. 9 | 10 | last 1 Chrome version 11 | last 1 Firefox version 12 | last 2 Edge major versions 13 | last 2 Safari major version 14 | last 2 iOS major versions 15 | Firefox ESR 16 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /apps/dummy-testing-library/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dummy-testing-library", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/dummy-testing-library/src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nrwl/webpack:webpack", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "compiler": "swc", 13 | "outputPath": "dist/apps/dummy-testing-library", 14 | "index": "apps/dummy-testing-library/src/index.html", 15 | "baseHref": "/", 16 | "main": "apps/dummy-testing-library/src/main.tsx", 17 | "polyfills": "apps/dummy-testing-library/src/polyfills.ts", 18 | "tsConfig": "apps/dummy-testing-library/tsconfig.app.json", 19 | "assets": [ 20 | "apps/dummy-testing-library/src/favicon.ico", 21 | "apps/dummy-testing-library/src/assets" 22 | ], 23 | "styles": ["apps/dummy-testing-library/src/styles.css"], 24 | "scripts": [], 25 | "webpackConfig": "@nrwl/react/plugins/webpack" 26 | }, 27 | "configurations": { 28 | "development": { 29 | "extractLicenses": false, 30 | "optimization": false, 31 | "sourceMap": true, 32 | "vendorChunk": true 33 | }, 34 | "production": { 35 | "fileReplacements": [ 36 | { 37 | "replace": "apps/dummy-testing-library/src/environments/environment.ts", 38 | "with": "apps/dummy-testing-library/src/environments/environment.prod.ts" 39 | } 40 | ], 41 | "optimization": true, 42 | "outputHashing": "all", 43 | "sourceMap": false, 44 | "namedChunks": false, 45 | "extractLicenses": true, 46 | "vendorChunk": false 47 | } 48 | } 49 | }, 50 | "serve": { 51 | "executor": "@nrwl/webpack:dev-server", 52 | "defaultConfiguration": "development", 53 | "options": { 54 | "buildTarget": "dummy-testing-library:build", 55 | "hmr": true 56 | }, 57 | "configurations": { 58 | "development": { 59 | "buildTarget": "dummy-testing-library:build:development" 60 | }, 61 | "production": { 62 | "buildTarget": "dummy-testing-library:build:production", 63 | "hmr": false 64 | } 65 | } 66 | }, 67 | "lint": { 68 | "executor": "@nrwl/linter:eslint", 69 | "outputs": ["{options.outputFile}"], 70 | "options": { 71 | "lintFilePatterns": ["apps/dummy-testing-library/**/*.{ts,tsx,js,jsx}"] 72 | } 73 | } 74 | }, 75 | "tags": [] 76 | } 77 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/app/App.module.css: -------------------------------------------------------------------------------- 1 | /* Your styles goes here. */ 2 | .container { 3 | margin: 1em; 4 | flex: 1; 5 | background-color: hsl(57, 1%, 98%); 6 | display: flex; 7 | flex-direction: column; 8 | overflow: auto; 9 | } 10 | 11 | .container > svg { 12 | border: 1px solid hsla(360, 0%, 50%, 0.2); 13 | } 14 | 15 | svg circle { 16 | stroke: white; 17 | stroke-width: 2px; 18 | } 19 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/app/App.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@mantine/core'; 2 | import { useElementSize } from '@mantine/hooks'; 3 | import { PayloadAction } from '@reduxjs/toolkit'; 4 | import { IProvenanceGraph, ProvenanceGraph, Trrack } from '@trrack/core'; 5 | import test from 'node:test'; 6 | import { useCallback, useEffect, useMemo, useState } from 'react'; 7 | 8 | import styles from './App.module.css'; 9 | import { GraphRenderer } from './components/graphRender'; 10 | import translate from './utils/translate'; 11 | 12 | function useGraph(state: T) { 13 | const { reducer, actions } = Trrack.createState({ 14 | initialState: 'Hello, World!', 15 | name: 'Test', 16 | reducers: { 17 | change(test, action: PayloadAction) { 18 | return action.payload; 19 | }, 20 | }, 21 | }); 22 | 23 | const t = Trrack.init({ 24 | reducer: { 25 | test: reducer, 26 | }, 27 | }); 28 | t.apply('A', actions.change('Bye')); 29 | t.apply('2', actions.change('Bye eqr')); 30 | t.apply('3', actions.change('Bye asd')); 31 | t.apply('4', actions.change('Bye 1')); 32 | 33 | const [provenance, setProvenance] = useState | null>( 34 | null 35 | ); 36 | 37 | useEffect(() => { 38 | if (provenance) return; 39 | 40 | const prov = ProvenanceGraph.create(state); 41 | 42 | prov.addAction('Hello', { 43 | do: { 44 | name: 'test', 45 | args: [1, 2], 46 | }, 47 | undo: { 48 | name: 'test', 49 | args: [1, 2], 50 | }, 51 | }); 52 | 53 | setProvenance(prov); 54 | }, [provenance, state]); 55 | 56 | const backend = useMemo(() => { 57 | return provenance?.backend; 58 | }, [provenance?.backend]); 59 | 60 | return { graph: provenance, backend }; 61 | } 62 | 63 | export function App() { 64 | const { ref, width, height } = useElementSize(); 65 | const { graph, backend } = useGraph({ helloTo: 'World' }); 66 | const [a, setA] = useState(Math.random()); 67 | 68 | const key = useCallback(() => { 69 | if (!a) return ''; 70 | return graph?.current.id || 'None'; 71 | }, [graph, a]); 72 | 73 | return ( 74 |
75 |
76 | 93 | 94 | 126 |
127 | {backend && ( 128 | 129 | 130 | 131 | 132 | 133 | )} 134 |
135 | ); 136 | } 137 | 138 | export default App; 139 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/app/components/graphRender.tsx: -------------------------------------------------------------------------------- 1 | import { Graph, IActionNode, IProvenanceNode, IStateNode } from '@trrack/core'; 2 | import { useMemo } from 'react'; 3 | 4 | import translate from '../utils/translate'; 5 | 6 | const SPACING = 150; 7 | const YSPACING = SPACING * 0.5; 8 | const NODE_SIZE = 6; 9 | 10 | function useStateNodes( 11 | graph: Graph 12 | ): Array<{ node: IStateNode; x: number; y: number }> { 13 | return graph 14 | .nodesBy>( 15 | (node) => (node as IProvenanceNode).type === 'State' 16 | ) 17 | .sort((a, b) => a.createdOn.getTime() - b.createdOn.getTime()) 18 | .map((node, idx) => ({ node, x: SPACING * idx, y: 0 })); 19 | } 20 | 21 | function useActionNodes(graph: Graph) { 22 | const allActionNodes = graph 23 | .nodesBy>( 24 | (node) => (node as IProvenanceNode).type === 'Action' 25 | ) 26 | .sort((a, b) => a.createdOn.getTime() - b.createdOn.getTime()); 27 | 28 | const actionNodes: Array<{ 29 | node: IActionNode; 30 | x: number; 31 | y: number; 32 | }> = []; 33 | const inverseActionNodes: Array<{ 34 | node: IActionNode; 35 | x: number; 36 | y: number; 37 | }> = []; 38 | 39 | allActionNodes 40 | .filter((node) => !node.isInverse) 41 | .forEach((node, idx) => { 42 | actionNodes.push({ 43 | node, 44 | x: SPACING / 2 + SPACING * idx, 45 | y: YSPACING, 46 | }); 47 | 48 | if (node.isInvertible) { 49 | const inverse = node.inverse; 50 | if (inverse) { 51 | inverseActionNodes.push({ 52 | node: inverse, 53 | x: SPACING / 2 + SPACING * idx, 54 | y: -YSPACING, 55 | }); 56 | } 57 | } 58 | }); 59 | 60 | return { 61 | actionNodes, 62 | inverseActionNodes, 63 | }; 64 | } 65 | 66 | export const GraphRenderer = ({ graph }: { graph: Graph }) => { 67 | const stateNodes = useStateNodes(graph); 68 | const { actionNodes, inverseActionNodes } = useActionNodes(graph); 69 | 70 | const nMap = useMemo(() => { 71 | const nm: { 72 | [key: string]: { node: IProvenanceNode; x: number; y: number }; 73 | } = {}; 74 | 75 | stateNodes.forEach((n) => (nm[n.node.id] = n)); 76 | actionNodes.forEach((n) => { 77 | if (n) nm[n.node.id] = n; 78 | }); 79 | inverseActionNodes.forEach((n) => { 80 | if (n) nm[n.node.id] = n; 81 | }); 82 | 83 | return nm; 84 | }, [stateNodes, actionNodes, inverseActionNodes]); 85 | 86 | const getX = (id: string) => { 87 | return nMap[id].x; 88 | }; 89 | 90 | const getY = (id: string) => { 91 | return nMap[id].y; 92 | }; 93 | 94 | return ( 95 | 96 | {Array.from(graph.edges.values()).map((edge) => ( 97 | 98 | {edge.type !== 'inverted_by' && edge.type !== 'inverts' ? ( 99 | 107 | ) : edge.type === 'inverts' ? ( 108 | 122 | ) : ( 123 | 137 | )} 138 | 139 | ))} 140 | 141 | {stateNodes.map(({ node, x, y }) => ( 142 | 143 | 144 | 150 | {node.label} 151 | 152 | 153 | ))} 154 | 155 | {actionNodes.map(({ node, x, y }) => { 156 | return ( 157 | node && ( 158 | 159 | 160 | 166 | {node.label} 167 | 168 | 169 | ) 170 | ); 171 | })} 172 | 173 | {inverseActionNodes.map(({ node, x, y }) => { 174 | return ( 175 | node && ( 176 | 177 | 178 | 184 | {node.label} 185 | 186 | 187 | ) 188 | ); 189 | })} 190 | 191 | ); 192 | }; 193 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/app/utils/translate.ts: -------------------------------------------------------------------------------- 1 | export default function translate(x: number, y = 0) { 2 | return `translate(${x}, ${y})`; 3 | } 4 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/dummy-testing-library/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/dummy-testing-library/src/favicon.ico -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DummyTestingLibrary 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import * as ReactDOM from 'react-dom/client'; 3 | 4 | import App from './app/App'; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById('root') as HTMLElement 8 | ); 9 | root.render( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | html, 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | #root { 9 | width: 100vw; 10 | height: 100vh; 11 | display: flex; 12 | } 13 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", 9 | "../../node_modules/@nrwl/react/typings/image.d.ts" 10 | ], 11 | "exclude": [ 12 | "jest.config.ts", 13 | "**/*.spec.ts", 14 | "**/*.test.ts", 15 | "**/*.spec.tsx", 16 | "**/*.test.tsx", 17 | "**/*.spec.js", 18 | "**/*.test.js", 19 | "**/*.spec.jsx", 20 | "**/*.test.jsx" 21 | ], 22 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/dummy-testing-library/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitOverride": true, 11 | "noPropertyAccessFromIndexSignature": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true 14 | }, 15 | "files": [], 16 | "include": [], 17 | "references": [ 18 | { 19 | "path": "./tsconfig.app.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /apps/react-trrack-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/react/babel", 5 | { 6 | "runtime": "automatic" 7 | } 8 | ] 9 | ], 10 | "plugins": [] 11 | } 12 | -------------------------------------------------------------------------------- /apps/react-trrack-example/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by: 2 | # 1. autoprefixer to adjust CSS to support the below specified browsers 3 | # 2. babel preset-env to adjust included polyfills 4 | # 5 | # For additional information regarding the format and rule options, please see: 6 | # https://github.com/browserslist/browserslist#queries 7 | # 8 | # If you need to support different browsers in production, you may tweak the list below. 9 | 10 | last 1 Chrome version 11 | last 1 Firefox version 12 | last 2 Edge major versions 13 | last 2 Safari major version 14 | last 2 iOS major versions 15 | Firefox ESR 16 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /apps/react-trrack-example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/react-trrack-example/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'react-trrack-example', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': [ 8 | '@swc/jest', 9 | { jsc: { transform: { react: { runtime: 'automatic' } } } }, 10 | ], 11 | }, 12 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 13 | coverageDirectory: '../../coverage/apps/react-trrack-example', 14 | }; 15 | -------------------------------------------------------------------------------- /apps/react-trrack-example/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-trrack-example", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/react-trrack-example/src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nrwl/webpack:webpack", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "compiler": "swc", 13 | "outputPath": "dist/apps/react-trrack-example", 14 | "index": "apps/react-trrack-example/src/index.html", 15 | "baseHref": "/", 16 | "main": "apps/react-trrack-example/src/main.tsx", 17 | "polyfills": "apps/react-trrack-example/src/polyfills.ts", 18 | "tsConfig": "apps/react-trrack-example/tsconfig.app.json", 19 | "assets": [ 20 | "apps/react-trrack-example/src/favicon.ico", 21 | "apps/react-trrack-example/src/assets" 22 | ], 23 | "styles": ["apps/react-trrack-example/src/styles.css"], 24 | "scripts": [], 25 | "webpackConfig": "@nrwl/react/plugins/webpack" 26 | }, 27 | "configurations": { 28 | "development": { 29 | "extractLicenses": false, 30 | "optimization": false, 31 | "sourceMap": true, 32 | "vendorChunk": true 33 | }, 34 | "production": { 35 | "fileReplacements": [ 36 | { 37 | "replace": "apps/react-trrack-example/src/environments/environment.ts", 38 | "with": "apps/react-trrack-example/src/environments/environment.prod.ts" 39 | } 40 | ], 41 | "optimization": true, 42 | "outputHashing": "all", 43 | "sourceMap": false, 44 | "namedChunks": false, 45 | "extractLicenses": true, 46 | "vendorChunk": false 47 | } 48 | } 49 | }, 50 | "serve": { 51 | "executor": "@nrwl/webpack:dev-server", 52 | "defaultConfiguration": "development", 53 | "options": { 54 | "buildTarget": "react-trrack-example:build", 55 | "hmr": true, 56 | "port": 3000 57 | }, 58 | "configurations": { 59 | "development": { 60 | "buildTarget": "react-trrack-example:build:development" 61 | }, 62 | "production": { 63 | "buildTarget": "react-trrack-example:build:production", 64 | "hmr": false 65 | } 66 | } 67 | }, 68 | "lint": { 69 | "executor": "@nrwl/linter:eslint", 70 | "outputs": ["{options.outputFile}"], 71 | "options": { 72 | "lintFilePatterns": ["apps/react-trrack-example/**/*.{ts,tsx,js,jsx}"] 73 | } 74 | }, 75 | "test": { 76 | "executor": "@nrwl/jest:jest", 77 | "outputs": ["{workspaceRoot}/coverage/apps/react-trrack-example"], 78 | "options": { 79 | "jestConfig": "apps/react-trrack-example/jest.config.ts", 80 | "passWithNoTests": true 81 | } 82 | } 83 | }, 84 | "tags": [] 85 | } 86 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/app/App.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Checkbox, List, ListItem, ListItemIcon, ListItemText, Typography } from '@mui/material'; 2 | import Tree, { useTreeState } from 'react-hyper-tree'; 3 | import { TreeNode } from 'react-hyper-tree/dist/helpers/node'; 4 | 5 | import { Navbar } from './components/Navbar'; 6 | import { useTrrackTaskManager } from './store/trrack'; 7 | 8 | function App() { 9 | const trrackManager = useTrrackTaskManager(); 10 | const { trrack } = trrackManager; 11 | const { actions } = trrackManager; 12 | 13 | const { required, handlers } = useTreeState({ 14 | data: trrackManager.trrack.tree(), 15 | id: 'test', 16 | defaultOpened: true, 17 | }); 18 | 19 | open(required.data, trrackManager.trrack.current.id); 20 | 21 | return ( 22 | 23 | 24 | 31 | 32 | {trrackManager.state.tasks.map((task) => ( 33 | 34 | 35 | { 39 | if (task.completed) 40 | trrack.apply( 41 | `Mark ${task.desc} as not done`, 42 | actions.markTaskIncomplete(task) 43 | ); 44 | else 45 | trrack.apply( 46 | `Mark ${task.desc} as done`, 47 | actions.markTaskComplete(task) 48 | ); 49 | }} 50 | /> 51 | 52 | 62 | {task.desc} 63 | 64 | } 65 | > 66 | 67 | ))} 68 | 69 | trrackManager.trrack.to(node.id)} 74 | /> 75 | 76 | 77 | ); 78 | } 79 | 80 | export default App; 81 | 82 | export function open(nodes: TreeNode[], current: string) { 83 | nodes.forEach((node) => { 84 | node.setSelected(current === node.id); 85 | node.setOpened(true); 86 | 87 | if (node.children) open(node.children, current); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/app/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import AddIcon from '@mui/icons-material/Add'; 2 | import RedoIcon from '@mui/icons-material/Redo'; 3 | import RemoveIcon from '@mui/icons-material/Remove'; 4 | import UndoIcon from '@mui/icons-material/Undo'; 5 | import { AppBar, Button, IconButton, Toolbar, Typography } from '@mui/material'; 6 | 7 | import { Trrack } from '../store/trrack'; 8 | import { Task } from '../store/types'; 9 | 10 | export function Navbar({ t }: { t: Trrack }) { 11 | const { trrack, isAtLatest, isAtRoot, actions, counter } = t; 12 | 13 | return ( 14 | <> 15 | 16 | 17 | 18 | Action based tracking with React 19 | 20 | { 22 | trrack.apply('Increment counter', actions.incrementCounter(1)); 23 | }} 24 | > 25 | 26 | 27 | 28 | Counter: {counter} 29 | 30 | { 32 | trrack.apply('Decrement counter', actions.decrementCounter(1)); 33 | }} 34 | > 35 | 36 | 37 | 55 | 64 | 73 | 74 | 75 | 76 | 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/app/store/trrack.ts: -------------------------------------------------------------------------------- 1 | import { initializeTrrack, Registry } from '@trrack/core'; 2 | import { useMemo, useState } from 'react'; 3 | 4 | import { Task } from './types'; 5 | 6 | const initialState = { 7 | tasks: [] as Task[], 8 | }; 9 | 10 | type State = typeof initialState; 11 | 12 | // ! Add example for async action (e.g. data loading) 13 | export function useTrrackTaskManager() { 14 | const [counter, setCounter] = useState(0); 15 | const [state, setState] = useState(initialState); 16 | 17 | const { registry, actions } = useMemo(() => { 18 | const reg = Registry.create(); 19 | 20 | const addTask = reg.register('add-task', (state, task: Task) => { 21 | state.tasks.push(task); 22 | }); 23 | 24 | const removeTask = reg.register('remove-task', (state, task: Task) => { 25 | state.tasks = state.tasks.filter((t: Task) => t.id !== task.id); 26 | }); 27 | 28 | const markTaskComplete = reg.register( 29 | 'complete-task', 30 | (state, task: Task) => { 31 | const idx = state.tasks.findIndex((d: any) => d.id === task.id); 32 | state.tasks[idx].completed = true; 33 | } 34 | ); 35 | 36 | const markTaskIncomplete = reg.register( 37 | 'incomplete-task', 38 | (state, task: Task) => { 39 | const idx = state.tasks.findIndex((d: any) => d.id === task.id); 40 | state.tasks[idx].completed = false; 41 | } 42 | ); 43 | 44 | const incrementCounter = reg.register( 45 | 'increment-counter', 46 | (add: number) => { 47 | setCounter((c) => c + add); 48 | return { 49 | undo: { 50 | type: 'decrement-counter', 51 | payload: add, 52 | meta: { 53 | hasSideEffects: true, 54 | }, 55 | } 56 | }; 57 | } 58 | ); 59 | 60 | const decrementCounter = reg.register( 61 | 'decrement-counter', 62 | (sub: number) => { 63 | setCounter((c) => c - sub); 64 | return { 65 | undo: { 66 | type: 'increment-counter', 67 | payload: sub, 68 | meta: { 69 | hasSideEffects: true, 70 | }, 71 | } 72 | }; 73 | } 74 | ); 75 | 76 | return { 77 | registry: reg, 78 | actions: { 79 | addTask, 80 | removeTask, 81 | markTaskComplete, 82 | markTaskIncomplete, 83 | incrementCounter, 84 | decrementCounter, 85 | }, 86 | }; 87 | }, []); 88 | 89 | const trrack = useMemo(() => { 90 | const t = initializeTrrack({ 91 | initialState, 92 | registry, 93 | }); 94 | 95 | t.currentChange(() => { 96 | setState(t.current.state.val as State); 97 | }); 98 | 99 | return t; 100 | }, [registry]); 101 | 102 | const current = useMemo(() => { 103 | return trrack.current; 104 | }, [trrack]); 105 | 106 | const isAtLatest = current ? current.children.length === 0 : false; 107 | 108 | const isAtRoot = current ? current.id === trrack.root.id : false; 109 | 110 | return { 111 | trrack, 112 | isAtLatest, 113 | isAtRoot, 114 | state, 115 | counter, 116 | actions, 117 | }; 118 | } 119 | 120 | export type Trrack = ReturnType; 121 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/app/store/types.ts: -------------------------------------------------------------------------------- 1 | export type Task = { 2 | id: string; 3 | taskNumber: number; 4 | createdOn: number; 5 | desc: string; 6 | completed: boolean; 7 | }; 8 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/react-trrack-example/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/react-trrack-example/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/react-trrack-example/src/favicon.ico -------------------------------------------------------------------------------- /apps/react-trrack-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactTrrackExample 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import * as ReactDOM from 'react-dom/client'; 3 | 4 | import App from './app/App'; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById('root') as HTMLElement 8 | ); 9 | root.render( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /apps/react-trrack-example/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /apps/react-trrack-example/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", 9 | "../../node_modules/@nrwl/react/typings/image.d.ts" 10 | ], 11 | "exclude": [ 12 | "jest.config.ts", 13 | "**/*.spec.ts", 14 | "**/*.test.ts", 15 | "**/*.spec.tsx", 16 | "**/*.test.tsx", 17 | "**/*.spec.js", 18 | "**/*.test.js", 19 | "**/*.spec.jsx", 20 | "**/*.test.jsx" 21 | ], 22 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/react-trrack-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitOverride": true, 11 | "noPropertyAccessFromIndexSignature": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noImplicitAny": false 15 | }, 16 | "files": [], 17 | "include": [], 18 | "references": [ 19 | { 20 | "path": "./tsconfig.app.json" 21 | }, 22 | { 23 | "path": "./tsconfig.spec.json" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /apps/react-trrack-example/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.ts", 12 | "**/*.test.tsx", 13 | "**/*.spec.tsx", 14 | "**/*.test.js", 15 | "**/*.spec.js", 16 | "**/*.test.jsx", 17 | "**/*.spec.jsx", 18 | "**/*.d.ts" 19 | ], 20 | "files": [ 21 | "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", 22 | "../../node_modules/@nrwl/react/typings/image.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/react/babel", 5 | { 6 | "runtime": "automatic" 7 | } 8 | ] 9 | ], 10 | "plugins": [] 11 | } 12 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by: 2 | # 1. autoprefixer to adjust CSS to support the below specified browsers 3 | # 2. babel preset-env to adjust included polyfills 4 | # 5 | # For additional information regarding the format and rule options, please see: 6 | # https://github.com/browserslist/browserslist#queries 7 | # 8 | # If you need to support different browsers in production, you may tweak the list below. 9 | 10 | last 1 Chrome version 11 | last 1 Firefox version 12 | last 2 Edge major versions 13 | last 2 Safari major version 14 | last 2 iOS major versions 15 | Firefox ESR 16 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /apps/rtk-trrack-example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'rtk-trrack-example', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': [ 8 | '@swc/jest', 9 | { jsc: { transform: { react: { runtime: 'automatic' } } } }, 10 | ], 11 | }, 12 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 13 | coverageDirectory: '../../coverage/apps/rtk-trrack-example', 14 | }; 15 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtk-trrack-example", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/rtk-trrack-example/src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nrwl/webpack:webpack", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "compiler": "swc", 13 | "outputPath": "dist/apps/rtk-trrack-example", 14 | "index": "apps/rtk-trrack-example/src/index.html", 15 | "baseHref": "/", 16 | "main": "apps/rtk-trrack-example/src/main.tsx", 17 | "polyfills": "apps/rtk-trrack-example/src/polyfills.ts", 18 | "tsConfig": "apps/rtk-trrack-example/tsconfig.app.json", 19 | "assets": [ 20 | "apps/rtk-trrack-example/src/favicon.ico", 21 | "apps/rtk-trrack-example/src/assets" 22 | ], 23 | "styles": ["apps/rtk-trrack-example/src/styles.css"], 24 | "scripts": [], 25 | "webpackConfig": "@nrwl/react/plugins/webpack" 26 | }, 27 | "configurations": { 28 | "development": { 29 | "extractLicenses": false, 30 | "optimization": false, 31 | "sourceMap": true, 32 | "vendorChunk": true 33 | }, 34 | "production": { 35 | "fileReplacements": [ 36 | { 37 | "replace": "apps/rtk-trrack-example/src/environments/environment.ts", 38 | "with": "apps/rtk-trrack-example/src/environments/environment.prod.ts" 39 | } 40 | ], 41 | "optimization": true, 42 | "outputHashing": "all", 43 | "sourceMap": false, 44 | "namedChunks": false, 45 | "extractLicenses": true, 46 | "vendorChunk": false 47 | } 48 | } 49 | }, 50 | "serve": { 51 | "executor": "@nrwl/webpack:dev-server", 52 | "defaultConfiguration": "development", 53 | "options": { 54 | "buildTarget": "rtk-trrack-example:build", 55 | "hmr": true, 56 | "port": 3001 57 | }, 58 | "configurations": { 59 | "development": { 60 | "buildTarget": "rtk-trrack-example:build:development" 61 | }, 62 | "production": { 63 | "buildTarget": "rtk-trrack-example:build:production", 64 | "hmr": false 65 | } 66 | } 67 | }, 68 | "lint": { 69 | "executor": "@nrwl/linter:eslint", 70 | "outputs": ["{options.outputFile}"], 71 | "options": { 72 | "lintFilePatterns": ["apps/rtk-trrack-example/**/*.{ts,tsx,js,jsx}"] 73 | } 74 | }, 75 | "test": { 76 | "executor": "@nrwl/jest:jest", 77 | "outputs": ["{workspaceRoot}/coverage/apps/rtk-trrack-example"], 78 | "options": { 79 | "jestConfig": "apps/rtk-trrack-example/jest.config.ts", 80 | "passWithNoTests": true 81 | } 82 | } 83 | }, 84 | "tags": [] 85 | } 86 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/app/App.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Checkbox, List, ListItem, ListItemIcon, ListItemText, Typography } from '@mui/material'; 2 | import React, { useEffect } from 'react'; 3 | import { Tree, useTreeState } from 'react-hyper-tree'; 4 | import { TreeNode } from 'react-hyper-tree/dist/helpers/node'; 5 | import { useDispatch, useSelector } from 'react-redux'; 6 | 7 | import { useTrrackSelector } from '../main'; 8 | import { Navbar } from './components/Navbar'; 9 | import { setTodoStatus } from './features/todo/taskSlice'; 10 | import { AppDispatch, RootState, trrack } from './store/store'; 11 | 12 | function App() { 13 | const tasks = useSelector((state: RootState) => state.tasks); 14 | const dispatch = useDispatch(); 15 | const current = useTrrackSelector((state: any) => state.current); 16 | const post = useSelector((state: RootState) => state.post); 17 | 18 | useEffect(() => { 19 | const url = new URLSearchParams(window.location.search); 20 | const importString = url.get('prov'); 21 | if (importString) { 22 | const g = JSON.parse(importString); 23 | if (trrack.root.id !== g.root) { 24 | trrack.import(importString); 25 | } 26 | } 27 | }, []); 28 | 29 | const { required, handlers } = useTreeState({ 30 | data: trrack.tree(), 31 | id: 'test', 32 | defaultOpened: true, 33 | }); 34 | 35 | open(required.data, current); 36 | 37 | return ( 38 | 39 | 40 | 47 | 48 | {tasks.map((task) => ( 49 | 50 | 51 | { 55 | dispatch( 56 | setTodoStatus({ 57 | id: task.id, 58 | completed: !task.completed, 59 | }) 60 | ); 61 | }} 62 | /> 63 | 64 | 74 | {task.description} 75 | 76 | } 77 | > 78 | 79 | ))} 80 | 81 | 82 | trrack.to(node.id)} 87 | /> 88 | 89 |
{JSON.stringify(post, null, 2)}
90 |
91 | ); 92 | } 93 | 94 | export default App; 95 | 96 | export function open(nodes: TreeNode[], current: string) { 97 | nodes.forEach((node) => { 98 | node.setSelected(current === node.id); 99 | node.setOpened(true); 100 | 101 | if (node.children) open(node.children, current); 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/app/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import AddIcon from '@mui/icons-material/Add'; 2 | import RedoIcon from '@mui/icons-material/Redo'; 3 | import RemoveIcon from '@mui/icons-material/Remove'; 4 | import UndoIcon from '@mui/icons-material/Undo'; 5 | import { AppBar, Button, IconButton, Toolbar, Typography } from '@mui/material'; 6 | import { useDispatch, useSelector } from 'react-redux'; 7 | 8 | import { decrement, increment } from '../features/counter/counterSlice'; 9 | import { getPostById } from '../features/posts/postSlice'; 10 | import { addTodo } from '../features/todo/taskSlice'; 11 | import { AppDispatch, RootState, trrack } from '../store/store'; 12 | 13 | export const Navbar = () => { 14 | const counter = useSelector((s) => s.counter.counter); 15 | const dispatch = useDispatch(); 16 | const post = useSelector((state: RootState) => state.post); 17 | 18 | return ( 19 | <> 20 | 21 | 22 | 23 | Action based tracking with Redux-Toolkit 24 | 25 | 37 | 46 | 47 | { 49 | dispatch(increment()); 50 | }} 51 | > 52 | 53 | 54 | 55 | Counter: {counter as any} 56 | 57 | { 59 | dispatch(decrement()); 60 | }} 61 | > 62 | 63 | 64 | 65 | 74 | 83 | 92 | 93 | 94 | 95 | 96 | ); 97 | }; 98 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/app/features/counter/counterSlice.ts: -------------------------------------------------------------------------------- 1 | import { createTrrackableSlice } from '@trrack/redux'; 2 | 3 | import { Counter, counterInitState } from './types'; 4 | 5 | export const counterSlice = createTrrackableSlice({ 6 | name: 'counter', 7 | initialState: counterInitState, 8 | reducers: { 9 | increment(state: Counter) { 10 | state.counter += 1; 11 | }, 12 | decrement(state: Counter) { 13 | state.counter -= 1; 14 | }, 15 | }, 16 | doUndoActionCreators: { 17 | increment: () => { 18 | return { 19 | undo: decrement(), 20 | }; 21 | }, 22 | decrement: () => { 23 | return { 24 | undo: increment(), 25 | }; 26 | }, 27 | }, 28 | }); 29 | 30 | export const { increment, decrement } = counterSlice.actions as any; 31 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/app/features/counter/types.ts: -------------------------------------------------------------------------------- 1 | export const counterInitState = { 2 | counter: 0, 3 | }; 4 | 5 | export type Counter = typeof counterInitState; 6 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/app/features/posts/postSlice.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk } from '@reduxjs/toolkit'; 2 | import { asyncDoUndoActionCreatorHelper, createTrrackableSlice } from '@trrack/redux'; 3 | 4 | export const getPostById = createAsyncThunk( 5 | 'post/getpostById', 6 | async (postId: number, _api) => { 7 | if (postId < 1) { 8 | return Promise.resolve({ 9 | userId: -1, 10 | id: -1, 11 | title: '', 12 | body: '', 13 | }); 14 | } 15 | 16 | const response = await fetch( 17 | `https://jsonplaceholder.typicode.com/posts/${postId}` 18 | ); 19 | 20 | return response.json(); 21 | } 22 | ); 23 | 24 | export const postSlice = createTrrackableSlice({ 25 | name: 'post', 26 | initialState: { 27 | userId: -1, 28 | id: -1, 29 | title: '', 30 | body: '', 31 | }, 32 | reducers: {}, 33 | asyncThunks: [getPostById], 34 | extraReducers(builder) { 35 | builder.addCase(getPostById.fulfilled, (_, action) => { 36 | return action.payload; 37 | }); 38 | }, 39 | doUndoActionCreators: { 40 | [getPostById.typePrefix]: ({ action }) => { 41 | return { 42 | do: asyncDoUndoActionCreatorHelper( 43 | getPostById.typePrefix, 44 | action.payload.id 45 | ), 46 | undo: asyncDoUndoActionCreatorHelper( 47 | getPostById.typePrefix, 48 | action.payload.id - 1 49 | ), 50 | }; 51 | }, 52 | }, 53 | }); 54 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/app/features/todo/taskSlice.ts: -------------------------------------------------------------------------------- 1 | import { PayloadAction } from '@reduxjs/toolkit'; 2 | import { createTrrackableSlice } from '@trrack/redux'; 3 | import { v4 as uuid } from 'uuid'; 4 | 5 | import { Todo } from './types'; 6 | 7 | const initialState: Todo[] = []; 8 | 9 | export const tasksSlice = createTrrackableSlice({ 10 | name: 'tasks', 11 | initialState, 12 | reducers: { 13 | addTodo: { 14 | reducer: (state, action: PayloadAction) => { 15 | state.push(action.payload); 16 | }, 17 | prepare: (description: string) => { 18 | return { 19 | payload: { 20 | id: uuid(), 21 | description, 22 | completed: false, 23 | }, 24 | }; 25 | }, 26 | }, 27 | removeTodo(state, action: PayloadAction) { 28 | const index = state.findIndex((t) => t.id === action.payload); 29 | state.splice(index, 1); 30 | }, 31 | setTodoStatus( 32 | state, 33 | action: PayloadAction<{ id: string; completed: boolean }> 34 | ) { 35 | const index = state.findIndex((t) => t.id === action.payload.id); 36 | state[index].completed = action.payload.completed; 37 | }, 38 | }, 39 | labels: { 40 | setTodoStatus: (payload: { id: string; completed: boolean }) => { 41 | return `Mark ${payload.id} as ${ 42 | payload.completed ? 'complete' : 'incomplete' 43 | }`; 44 | }, 45 | addTodo: ((task: Todo) => { 46 | return `Add task ${task.description}`; 47 | }) as any, 48 | }, 49 | }); 50 | 51 | export const { addTodo, setTodoStatus, removeTodo } = tasksSlice.actions; 52 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/app/features/todo/types.ts: -------------------------------------------------------------------------------- 1 | export type Todo = { 2 | id: string; 3 | description: string; 4 | completed: boolean; 5 | }; 6 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/app/store/store.ts: -------------------------------------------------------------------------------- 1 | import { configureTrrackableStore } from '@trrack/redux'; 2 | 3 | import { counterSlice } from '../features/counter/counterSlice'; 4 | import { postSlice } from '../features/posts/postSlice'; 5 | import { tasksSlice } from '../features/todo/taskSlice'; 6 | 7 | export const { store, trrack, trrackStore } = configureTrrackableStore({ 8 | reducer: { 9 | tasks: tasksSlice.reducer, 10 | counter: counterSlice.reducer, 11 | post: postSlice.reducer, 12 | }, 13 | slices: [counterSlice, tasksSlice, postSlice], 14 | }); 15 | 16 | export type AppDispatch = typeof store.dispatch; 17 | export type RootState = ReturnType; 18 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/rtk-trrack-example/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/rtk-trrack-example/src/favicon.ico -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RtkTrrackExample 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { TrrackStoreType } from '@trrack/redux'; 2 | import { createContext, StrictMode } from 'react'; 3 | import * as ReactDOM from 'react-dom/client'; 4 | import { createSelectorHook, Provider } from 'react-redux'; 5 | 6 | import App from './app/App'; 7 | import { store, trrackStore } from './app/store/store'; 8 | 9 | const trrackContext = createContext(undefined!); 10 | export const useTrrackSelector = createSelectorHook(trrackContext as any); 11 | 12 | const root = ReactDOM.createRoot( 13 | document.getElementById('root') as HTMLElement 14 | ); 15 | 16 | root.render( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", 9 | "../../node_modules/@nrwl/react/typings/image.d.ts" 10 | ], 11 | "exclude": [ 12 | "jest.config.ts", 13 | "**/*.spec.ts", 14 | "**/*.test.ts", 15 | "**/*.spec.tsx", 16 | "**/*.test.tsx", 17 | "**/*.spec.js", 18 | "**/*.test.js", 19 | "**/*.spec.jsx", 20 | "**/*.test.jsx" 21 | ], 22 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitOverride": true, 11 | "noPropertyAccessFromIndexSignature": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true 14 | }, 15 | "files": [], 16 | "include": [], 17 | "references": [ 18 | { 19 | "path": "./tsconfig.app.json" 20 | }, 21 | { 22 | "path": "./tsconfig.spec.json" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /apps/rtk-trrack-example/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.ts", 12 | "**/*.test.tsx", 13 | "**/*.spec.tsx", 14 | "**/*.test.js", 15 | "**/*.spec.js", 16 | "**/*.test.jsx", 17 | "**/*.spec.jsx", 18 | "**/*.d.ts" 19 | ], 20 | "files": [ 21 | "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", 22 | "../../node_modules/@nrwl/react/typings/image.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@nrwl/js/babel" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # If you need to support different browsers in production, you may tweak the list below. 6 | 7 | last 1 Chrome version 8 | last 1 Firefox version 9 | last 2 Edge major versions 10 | last 2 Safari major version 11 | last 2 iOS major versions 12 | Firefox ESR 13 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /apps/trrack-lineup-example/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'trrack-lineup-example', 4 | preset: '../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | transform: { 7 | '^.+\\.[tj]s$': '@swc/jest', 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/apps/trrack-lineup-example', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trrack-lineup-example", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/trrack-lineup-example/src", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nrwl/webpack:webpack", 10 | "outputs": ["{options.outputPath}"], 11 | "defaultConfiguration": "production", 12 | "options": { 13 | "outputPath": "dist/apps/trrack-lineup-example", 14 | "compiler": "swc", 15 | "index": "apps/trrack-lineup-example/src/index.html", 16 | "baseHref": "/", 17 | "main": "apps/trrack-lineup-example/src/main.ts", 18 | "polyfills": "apps/trrack-lineup-example/src/polyfills.ts", 19 | "tsConfig": "apps/trrack-lineup-example/tsconfig.app.json", 20 | "assets": [ 21 | "apps/trrack-lineup-example/src/favicon.ico", 22 | "apps/trrack-lineup-example/src/assets" 23 | ], 24 | "styles": ["apps/trrack-lineup-example/src/styles.css"], 25 | "scripts": [] 26 | }, 27 | "configurations": { 28 | "production": { 29 | "fileReplacements": [ 30 | { 31 | "replace": "apps/trrack-lineup-example/src/environments/environment.ts", 32 | "with": "apps/trrack-lineup-example/src/environments/environment.prod.ts" 33 | } 34 | ], 35 | "optimization": true, 36 | "outputHashing": "all", 37 | "sourceMap": false, 38 | "namedChunks": false, 39 | "extractLicenses": true, 40 | "vendorChunk": false 41 | } 42 | } 43 | }, 44 | "serve": { 45 | "executor": "@nrwl/webpack:dev-server", 46 | "options": { 47 | "buildTarget": "trrack-lineup-example:build" 48 | }, 49 | "configurations": { 50 | "production": { 51 | "buildTarget": "trrack-lineup-example:build:production" 52 | } 53 | } 54 | }, 55 | "lint": { 56 | "executor": "@nrwl/linter:eslint", 57 | "outputs": ["{options.outputFile}"], 58 | "options": { 59 | "lintFilePatterns": ["apps/trrack-lineup-example/**/*.ts"] 60 | } 61 | }, 62 | "test": { 63 | "executor": "@nrwl/jest:jest", 64 | "outputs": ["{workspaceRoot}/coverage/apps/trrack-lineup-example"], 65 | "options": { 66 | "jestConfig": "apps/trrack-lineup-example/jest.config.ts", 67 | "passWithNoTests": true 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/app/app.element.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Remove template code below 3 | */ 4 | html { 5 | -webkit-text-size-adjust: 100%; 6 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 7 | 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 8 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 9 | line-height: 1.5; 10 | tab-size: 4; 11 | scroll-behavior: smooth; 12 | } 13 | body { 14 | font-family: inherit; 15 | line-height: inherit; 16 | margin: 0; 17 | } 18 | h1, 19 | h2, 20 | p, 21 | pre { 22 | margin: 0; 23 | } 24 | *, 25 | ::before, 26 | ::after { 27 | box-sizing: border-box; 28 | border-width: 0; 29 | border-style: solid; 30 | border-color: currentColor; 31 | } 32 | h1, 33 | h2 { 34 | font-size: inherit; 35 | font-weight: inherit; 36 | } 37 | a { 38 | color: inherit; 39 | text-decoration: inherit; 40 | } 41 | pre { 42 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 43 | 'Liberation Mono', 'Courier New', monospace; 44 | } 45 | svg { 46 | display: block; 47 | vertical-align: middle; 48 | } 49 | 50 | svg { 51 | shape-rendering: auto; 52 | text-rendering: optimizeLegibility; 53 | } 54 | pre { 55 | background-color: rgba(55, 65, 81, 1); 56 | border-radius: 0.25rem; 57 | color: rgba(229, 231, 235, 1); 58 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 59 | 'Liberation Mono', 'Courier New', monospace; 60 | overflow: scroll; 61 | padding: 0.5rem 0.75rem; 62 | } 63 | 64 | .wrapper { 65 | width: 100vw; 66 | height: 100vh; 67 | } 68 | .container { 69 | height: 100%; 70 | display: grid; 71 | grid-template-rows: min-content auto; 72 | margin: 0 auto; 73 | max-width: 95vw; 74 | padding-left: 1rem; 75 | padding-right: 1rem; 76 | padding-bottom: 1em; 77 | color: rgba(55, 65, 81, 1); 78 | } 79 | 80 | #welcome { 81 | margin-top: 2.5rem; 82 | } 83 | 84 | #welcome h1 { 85 | font-size: 3rem; 86 | font-weight: 500; 87 | letter-spacing: -0.025em; 88 | line-height: 1; 89 | } 90 | 91 | #lineup-wrapper { 92 | padding: 1em; 93 | } 94 | 95 | #lineup-root { 96 | height: 100%; 97 | width: 100%; 98 | } 99 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/app/app.element.ts: -------------------------------------------------------------------------------- 1 | import './app.element.css'; 2 | 3 | const lineupRootSelector = 'lineup-root'; 4 | 5 | export class AppElement extends HTMLElement { 6 | public static observedAttributes = []; 7 | 8 | connectedCallback() { 9 | this.innerHTML = ` 10 |
11 |
12 | 13 |
14 |

15 | Action-based Trrack LineUp Example 16 |

17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 | `; 36 | } 37 | } 38 | customElements.define('trrack-root', AppElement); 39 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/app/lineup-manager.ts: -------------------------------------------------------------------------------- 1 | import { initializeTrrack, Registry } from '@trrack/core'; 2 | import { 3 | Column, 4 | EngineRenderer, 5 | ISortCriteria, 6 | LineUp, 7 | Ranking, 8 | } from 'lineupjs'; 9 | 10 | // * To Track: 11 | // * https://github.com/datavisyn/tdp_core/blob/49700025e732529bd2592e8371c29d2f1bdf51ee/src/lineup/internal/cmds.ts 12 | /** 13 | * CMD_SET_SORTING_CRITERIA = 'lineupSetRankingSortCriteria', - done 14 | * CMD_SET_GROUP_CRITERIA = 'lineupSetGroupCriteria', - done 15 | * CMD_SET_COLUMN = 'lineupSetColumn', - working 16 | * CMD_ADD_COLUMN = 'lineupAddColumn', - working 17 | * CMD_MOVE_COLUMN = 'lineupMoveColumn', - working 18 | * CMD_SET_AGGREGATION = 'lineupSetAggregation', - TODO 19 | * https://github.com/datavisyn/tdp_core/blob/93527f782845514b934e12b7b5f713b3cd5d930e/src/lineup/internal/cmds.ts#L891-L954 20 | */ 21 | 22 | /** 23 | * Tracking selections too 24 | */ 25 | 26 | // ! Add trrack suffix to all events 27 | 28 | function trrackedEvent(event: string) { 29 | return `${event}.trrack`; 30 | } 31 | 32 | type Trrack = ReturnType; 33 | 34 | function initDialogTracker() { 35 | let isOpen = false; 36 | 37 | return { 38 | isOpen, 39 | setOpen() { 40 | isOpen = true; 41 | }, 42 | setClose() { 43 | isOpen = false; 44 | }, 45 | }; 46 | } 47 | 48 | type DialogTracker = ReturnType; 49 | 50 | function dirtyRankingWaiter(ranking: Ranking) { 51 | const trrackDirtyOrder = `${Ranking.EVENT_DIRTY_ORDER}.trrack`; 52 | const trrackOrderChange = `${Ranking.EVENT_ORDER_CHANGED}.trrack`; 53 | 54 | return new Promise((res) => { 55 | ranking.on(trrackDirtyOrder, (a) => { 56 | ranking.on(trrackDirtyOrder, null); 57 | 58 | ranking.on(trrackOrderChange, () => { 59 | ranking.on(trrackOrderChange, null); 60 | res(null); 61 | }); 62 | }); 63 | }); 64 | } 65 | 66 | // ? Look at filter events in setColumn 67 | // ? https://github.com/datavisyn/tdp_core/blob/93527f782845514b934e12b7b5f713b3cd5d930e/src/lineup/internal/cmds.ts#L442 68 | 69 | function trrackSortLike( 70 | eventType: string, 71 | id: string, 72 | ranking: Ranking, 73 | handler: (previous: T, current: T) => void, 74 | executor: (arg: T) => void, 75 | registry: Registry 76 | ) { 77 | const suffixedEventType = `${eventType}.trrack`; 78 | 79 | ranking.on(suffixedEventType, handler); 80 | 81 | registry.register(id, (args: T) => { 82 | ranking.on(suffixedEventType, null); 83 | executor(args); 84 | ranking.on(suffixedEventType, handler); 85 | return dirtyRankingWaiter(ranking); 86 | }); 87 | } 88 | 89 | function trrackColumnEvents( 90 | lineupId: string, 91 | lineup: LineUp, 92 | ranking: Ranking, 93 | trrack: Trrack, 94 | registry: Registry 95 | ) { 96 | // TODO: Add column events 97 | } 98 | 99 | function trrackRanking( 100 | lineupId: string, 101 | lineupInstance: LineUp, 102 | ranking: Ranking, 103 | trrack: Trrack, 104 | registry: Registry 105 | ) { 106 | // Sort 107 | const sortEvent = `${lineupId}-${ranking.id}-${Ranking.EVENT_SORT_CRITERIA_CHANGED}`; 108 | trrackSortLike( 109 | Ranking.EVENT_SORT_CRITERIA_CHANGED, 110 | sortEvent, 111 | ranking, 112 | ( 113 | p: ISortCriteria | ISortCriteria[], 114 | c: ISortCriteria | ISortCriteria[] 115 | ) => { 116 | trrack.record({ 117 | label: 'Sort', 118 | eventType: 'Sort', 119 | state: null, 120 | sideEffects: { 121 | do: [{ type: sortEvent, payload: c }], 122 | undo: [{ type: sortEvent, payload: p }], 123 | }, 124 | }); 125 | }, 126 | (sortBy: ISortCriteria | ISortCriteria[]) => 127 | ranking.setSortCriteria(sortBy), 128 | registry 129 | ); 130 | 131 | const groupEvent = `${lineupId}-${ranking.id}-${Ranking.EVENT_GROUP_CRITERIA_CHANGED}`; 132 | trrackSortLike( 133 | Ranking.EVENT_GROUP_CRITERIA_CHANGED, 134 | groupEvent, 135 | ranking, 136 | (p: Column[], c: Column[]) => { 137 | trrack.record({ 138 | label: 'Group', 139 | eventType: 'Group', 140 | state: null, 141 | sideEffects: { 142 | do: [{ type: groupEvent, payload: c }], 143 | undo: [{ type: groupEvent, payload: p }], 144 | }, 145 | }); 146 | }, 147 | (col: Column[]) => { 148 | console.log(col); 149 | ranking.setGroupCriteria(col); 150 | }, 151 | registry 152 | ); 153 | 154 | trrackColumnEvents(lineupId, lineupInstance, ranking, trrack, registry); 155 | } 156 | 157 | function trrackLineUp( 158 | id: string, 159 | instance: LineUp, 160 | trrack: Trrack, 161 | registry: Registry 162 | ) { 163 | instance.data 164 | .getRankings() 165 | .forEach((r) => trrackRanking(id, instance, r, trrack, registry)); 166 | 167 | let previousSelection: number[] = []; 168 | 169 | const instanceSelectionId = `${id}-${LineUp.EVENT_SELECTION_CHANGED}`; 170 | const suffixedSelectionEvent = `${LineUp.EVENT_SELECTION_CHANGED}.trrack`; 171 | 172 | const selectionEventHandler = (selection: number[]) => { 173 | trrack.record({ 174 | label: 'Sort', 175 | eventType: 'Sort', 176 | state: null, 177 | sideEffects: { 178 | do: [{ type: instanceSelectionId, payload: selection }], 179 | undo: [{ type: instanceSelectionId, payload: previousSelection }], 180 | }, 181 | }); 182 | 183 | previousSelection = selection; 184 | }; 185 | 186 | instance.on(LineUp.EVENT_SELECTION_CHANGED, selectionEventHandler); 187 | 188 | registry.register(instanceSelectionId, (selections: number[]) => { 189 | instance.on(suffixedSelectionEvent, null); 190 | instance.setSelection(selections); 191 | instance.on(suffixedSelectionEvent, selectionEventHandler); 192 | }); 193 | } 194 | 195 | export function initLineupManager(trrack: Trrack, registry: Registry) { 196 | const instances: Map = new Map(); 197 | 198 | return { 199 | add(id: string, instance: LineUp) { 200 | trrackLineUp(id, instance, trrack, registry); 201 | instances.set(id, instance); 202 | }, 203 | print() { 204 | instances.forEach((a, i) => console.log({ i, a })); 205 | }, 206 | }; 207 | } 208 | 209 | function setupBuffer(instance: LineUp) { 210 | const dialog: DialogTracker = initDialogTracker(); 211 | 212 | const initialStates = new Map(); 213 | const bufferedActions = new Map(); 214 | 215 | function clearBuffer() { 216 | initialStates.clear(); 217 | bufferedActions.clear(); 218 | } 219 | 220 | instance.on(`${EngineRenderer.EVENT_DIALOG_OPENED}.trrack`, () => { 221 | dialog.setOpen(); 222 | clearBuffer(); 223 | }); 224 | 225 | // ! https://github.com/datavisyn/tdp_core/blob/93527f782845514b934e12b7b5f713b3cd5d930e/src/lineup/internal/cmds.ts#L862 226 | // ! Bug in clue wrapper 227 | // ! Filed the issue. Stopping the buffer for now. 228 | instance.on( 229 | `${EngineRenderer.EVENT_DIALOG_CLOSED}.trrack`, 230 | (_, dialogAction: 'cancel' | 'confirm') => { 231 | console.log('Dialog closed with:', dialogAction); 232 | } 233 | ); 234 | 235 | clearBuffer(); 236 | dialog.setClose(); 237 | 238 | // ? Add event to close dialog when running provenance actions 239 | // ? Same for execution 240 | 241 | return { 242 | clearBuffer, 243 | addToBuffer() { 244 | // TODO: Add to buffer 245 | }, 246 | isDialogOpen: () => dialog.isOpen, 247 | }; 248 | } 249 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/app/lineup-setup.ts: -------------------------------------------------------------------------------- 1 | import { initializeTrrack, Registry } from '@trrack/core'; 2 | import { 3 | buildCategoricalColumn, 4 | builder as bld, 5 | buildNumberColumn, 6 | buildStringColumn, 7 | } from 'lineupjs'; 8 | 9 | import { initLineupManager } from './lineup-manager'; 10 | 11 | const arr: any[] = []; 12 | const cats = ['c1', 'c2', 'c3']; 13 | for (let i = 0; i < 10000; ++i) { 14 | arr.push({ 15 | a: Math.random() * 10, 16 | d: 'Row ' + i, 17 | cat: cats[Math.floor(Math.random() * 3)], 18 | cat2: cats[Math.floor(Math.random() * 3)], 19 | }); 20 | } 21 | 22 | export function setup(node: HTMLElement[]) { 23 | const builder1 = bld(arr); 24 | 25 | builder1 26 | .column(buildStringColumn('d')) 27 | .column(buildCategoricalColumn('cat', cats)) 28 | .column(buildCategoricalColumn('cat2', cats)) 29 | .column(buildNumberColumn('a')); 30 | 31 | const builder2 = bld(arr); 32 | 33 | builder2 34 | .column(buildStringColumn('d')) 35 | .column(buildCategoricalColumn('cat', cats)) 36 | .column(buildCategoricalColumn('cat2', cats)) 37 | .column(buildNumberColumn('a')); 38 | 39 | const registry = Registry.create(); 40 | const trrack = initializeTrrack({ 41 | registry, 42 | initialState: null, 43 | }); 44 | 45 | const manager = initLineupManager(trrack, registry); 46 | 47 | manager.add('first', builder1.build(node[0])); 48 | manager.add('second', builder2.build(node[1])); 49 | 50 | console.log(trrack.registry); 51 | 52 | document.querySelector('#undo').onclick = (e) => { 53 | trrack.undo(); 54 | }; 55 | 56 | document.querySelector('#redo').onclick = (e) => { 57 | trrack.redo(); 58 | }; 59 | 60 | document.querySelector('#log').onclick = (e) => { 61 | console.table((trrack as any).graph.nodes); 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/trrack-lineup-example/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/trrack-lineup-example/src/favicon.ico -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TrrackLineupExample 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/main.ts: -------------------------------------------------------------------------------- 1 | import './app/app.element.ts'; 2 | 3 | import { setup } from './app/lineup-setup'; 4 | 5 | const lineupRootSelector = 'lineup-root'; 6 | const node1 = document.getElementById(`${lineupRootSelector}-1`); 7 | const node2 = document.getElementById(`${lineupRootSelector}-2`); 8 | 9 | if (node1 && node2) { 10 | setup([node1, node2]); 11 | } else { 12 | document.body.innerHTML = 'Something went wrong'; 13 | } 14 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import 'https://unpkg.com/lineupjs/build/LineUpJS.css'; 3 | 4 | #lineup-wrapper { 5 | display: grid; 6 | grid-template-rows: 1fr 1fr min-content; 7 | } 8 | 9 | .lineup-div { 10 | margin: 0.5em; 11 | } 12 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/src/test-setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/apps/trrack-lineup-example/src/test-setup.ts -------------------------------------------------------------------------------- /apps/trrack-lineup-example/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/trrack-lineup-example/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "babelrcRoots": ["*"] 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional', '@commitlint/config-nx-scopes'], 3 | }; 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 14 | 18 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from '@nrwl/jest'; 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'packages/**/*.{js,jsx,ts,tsx,json,html,css,scss}': [ 3 | 'nx affected:lint --uncommitted --fix=true', 4 | // 'nx affected:test --uncommitted', 5 | 'nx format:write --uncommitted', 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "runner": "@nrwl/nx-cloud", 5 | "options": { 6 | "cacheableOperations": ["build", "lint", "test", "e2e"], 7 | "accessToken": "MTVhOGI4MDItZWYwYi00ODJiLTkxY2UtYzFkZTY2NjA2NWIyfHJlYWQtd3JpdGU=" 8 | } 9 | } 10 | }, 11 | "pluginsConfig": { 12 | "@nrwl/js": { 13 | "analyzeSourceFiles": true 14 | } 15 | }, 16 | "extends": "nx/presets/npm.json", 17 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 18 | "npmScope": "trrack", 19 | "affected": { 20 | "defaultBase": "main" 21 | }, 22 | "workspaceLayout": { 23 | "appsDir": "apps", 24 | "libsDir": "packages" 25 | }, 26 | "cli": { 27 | "packageManager": "yarn" 28 | }, 29 | "generators": { 30 | "@nrwl/react": { 31 | "application": { 32 | "style": "css", 33 | "linter": "eslint", 34 | "babel": true 35 | }, 36 | "component": { 37 | "style": "css" 38 | }, 39 | "library": { 40 | "style": "css", 41 | "linter": "eslint" 42 | } 43 | }, 44 | "@nrwl/web:application": { 45 | "style": "css", 46 | "linter": "eslint", 47 | "unitTestRunner": "jest", 48 | "e2eTestRunner": "none" 49 | }, 50 | "@nrwl/web:library": { 51 | "style": "css", 52 | "linter": "eslint", 53 | "unitTestRunner": "jest" 54 | }, 55 | "@nrwl/next": { 56 | "application": { 57 | "style": "css", 58 | "linter": "eslint" 59 | } 60 | } 61 | }, 62 | "defaultProject": "core", 63 | "namedInputs": { 64 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 65 | "sharedGlobals": ["{workspaceRoot}/babel.config.json"], 66 | "production": [ 67 | "default", 68 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 69 | "!{projectRoot}/tsconfig.spec.json", 70 | "!{projectRoot}/jest.config.[jt]s", 71 | "!{projectRoot}/.eslintrc.json" 72 | ] 73 | }, 74 | "targetDefaults": { 75 | "build": { 76 | "inputs": ["production", "^production"], 77 | "dependsOn": ["^build"] 78 | }, 79 | "test": { 80 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"] 81 | }, 82 | "lint": { 83 | "inputs": ["default", "{workspaceRoot}/.eslintrc.json"] 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trrack", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "dev:all": "nx run-many --target=serve --projects=react-trrack-example,rtk-trrack-example --output-style=stream", 7 | "test:all:watch": "nx run-many --target=test --projects=core,redux --watch", 8 | "prepare": "husky install", 9 | "commit": "npx git-cz", 10 | "nx-create-migrations": "nx migrate latest", 11 | "nx-migrate": "nx migrate --run-migrations --create-commits", 12 | "test-affected-libs": "nx affected --target=test --exclude $(nx print-affected --type=app --select=projects)", 13 | "build-affected-libs": "nx affected:build --exclude $(nx print-affected --type=app --select=projects)", 14 | "release": "nx affected --target=release --exclude $(nx print-affected --type=app --select=projects)" 15 | }, 16 | "private": true, 17 | "dependencies": { 18 | "@mantine/core": "^4.2.12", 19 | "@mantine/hooks": "^4.2.12", 20 | "@nrwl/next": "15.6.3", 21 | "@reduxjs/toolkit": "^1.9.1", 22 | "@swc/helpers": "^0.4.0", 23 | "autoprefixer": "^10.4.13", 24 | "clsx": "^1.2.1", 25 | "core-js": "^3.6.5", 26 | "fast-json-patch": "^3.1.1", 27 | "next": "13.1.1", 28 | "nextra": "^2.0.1", 29 | "nextra-theme-docs": "^2.0.1", 30 | "postcss": "^8.4.20", 31 | "react": "18.2.0", 32 | "react-dom": "18.2.0", 33 | "regenerator-runtime": "0.13.7", 34 | "tailwindcss": "^3.2.4", 35 | "tslib": "^2.3.0", 36 | "uuid": "^9.0.0" 37 | }, 38 | "devDependencies": { 39 | "@babel/preset-react": "^7.14.5", 40 | "@commitlint/cli": "^17.4.2", 41 | "@commitlint/config-angular": "^17.4.2", 42 | "@commitlint/config-conventional": "^17.4.2", 43 | "@commitlint/config-nx-scopes": "^17.4.2", 44 | "@commitlint/cz-commitlint": "^17.4.2", 45 | "@emotion/react": "11.10.5", 46 | "@emotion/styled": "11.10.5", 47 | "@jscutlery/semver": "^2.25.2", 48 | "@mui/icons-material": "^5.8.0", 49 | "@mui/material": "^5.8.1", 50 | "@nrwl/cli": "15.6.3", 51 | "@nrwl/eslint-plugin-nx": "15.6.3", 52 | "@nrwl/jest": "15.6.3", 53 | "@nrwl/js": "15.6.3", 54 | "@nrwl/linter": "15.6.3", 55 | "@nrwl/nx-cloud": "15.0.3", 56 | "@nrwl/react": "15.6.3", 57 | "@nrwl/vite": "^15.6.3", 58 | "@nrwl/web": "15.6.3", 59 | "@nrwl/workspace": "15.6.3", 60 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", 61 | "@svgr/webpack": "^6.1.2", 62 | "@swc/cli": "~0.1.55", 63 | "@swc/core": "~1.2.143", 64 | "@swc/jest": "0.2.20", 65 | "@testing-library/react": "13.4.0", 66 | "@types/jest": "28.1.8", 67 | "@types/node": "18.11.9", 68 | "@types/react": "18.0.25", 69 | "@types/react-dom": "18.0.9", 70 | "@types/uuid": "^9.0.0", 71 | "@typescript-eslint/eslint-plugin": "5.42.0", 72 | "@typescript-eslint/parser": "5.42.0", 73 | "@vitejs/plugin-react": "^3.0.1", 74 | "@vitest/coverage-c8": "~0.25.8", 75 | "@vitest/ui": "^0.29.2", 76 | "commitizen": "^4.3.0", 77 | "css-loader": "^6.4.0", 78 | "eslint": "8.15.0", 79 | "eslint-config-next": "13.1.1", 80 | "eslint-config-prettier": "8.1.0", 81 | "eslint-plugin-import": "2.26.0", 82 | "eslint-plugin-jsx-a11y": "6.6.1", 83 | "eslint-plugin-react": "7.31.11", 84 | "eslint-plugin-react-hooks": "4.6.0", 85 | "husky": "^8.0.0", 86 | "jest": "28.1.3", 87 | "jsdom": "~20.0.3", 88 | "lineupjs": "^4.6.1", 89 | "lint-staged": "^13.1.0", 90 | "ngx-deploy-npm": "^4.1.3", 91 | "nx": "15.6.3", 92 | "prettier": "2.7.1", 93 | "react-hyper-tree": "^0.3.12", 94 | "react-redux": "8.0.5", 95 | "react-refresh": "^0.10.0", 96 | "react-test-renderer": "18.2.0", 97 | "semantic-release-npm-github-publish": "^1.5.4", 98 | "semantic-release-plus": "^20.0.0", 99 | "sharp": "^0.31.2", 100 | "style-loader": "^3.3.0", 101 | "stylus": "^0.55.0", 102 | "stylus-loader": "^7.1.0", 103 | "swc-loader": "0.1.15", 104 | "ts-jest": "28.0.8", 105 | "ts-node": "10.9.1", 106 | "typescript": "4.8.4", 107 | "url-loader": "^4.1.1", 108 | "vite": "^4.0.1", 109 | "vite-plugin-dts": "~1.7.1", 110 | "vite-plugin-eslint": "^1.8.1", 111 | "vite-tsconfig-paths": "^4.0.2", 112 | "vitest": "^0.28.3", 113 | "webpack": "^5.75.0", 114 | "webpack-merge": "^5.8.0" 115 | }, 116 | "workspaces": [ 117 | "packages/**", 118 | "apps/**" 119 | ], 120 | "config": { 121 | "commitizen": { 122 | "path": "git-cz" 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /packages/core/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/js/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /packages/core/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # core 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build core` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test core` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trrack/core", 3 | "version": "0.0.0-semantic-release", 4 | "main": "./index.js", 5 | "types": "./index.d.ts", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Trrack/trrackjs.git" 9 | }, 10 | "dependencies": { 11 | "fast-json-patch": "^3.1.1", 12 | "uuid": "^9.0.0", 13 | "@reduxjs/toolkit": "^1.9.1" 14 | }, 15 | "peerDependencies": { 16 | "fast-json-patch": "^3.1.1", 17 | "uuid": "^9.0.0", 18 | "@reduxjs/toolkit": "^1.9.1" 19 | }, 20 | "exports": { 21 | ".": { 22 | "import": "./index.mjs", 23 | "require": "./index.js" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "core", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/core/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nrwl/vite:build", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "outputPath": "dist/packages/core" 13 | }, 14 | "configurations": { 15 | "development": { 16 | "mode": "development" 17 | }, 18 | "production": { 19 | "mode": "production" 20 | } 21 | } 22 | }, 23 | "release": { 24 | "executor": "nx:run-commands", 25 | "outputs": [], 26 | "options": { 27 | "command": "npx semantic-release-plus --extends ./packages/core/release.config.js", 28 | "parallel": false 29 | } 30 | }, 31 | "publish": { 32 | "executor": "nx:run-commands", 33 | "options": { 34 | "command": "node tools/scripts/publish.mjs core {args.ver} {args.tag}" 35 | }, 36 | "dependsOn": ["build"] 37 | }, 38 | "test": { 39 | "executor": "@nrwl/vite:test", 40 | "outputs": ["{workspaceRoot}/coverage/packages/core"], 41 | "options": { 42 | "passWithNoTests": true, 43 | "reportsDirectory": "../../coverage/packages/core" 44 | }, 45 | "configurations": { 46 | "dev": { 47 | "watch": true, 48 | "ui": true 49 | } 50 | } 51 | }, 52 | "lint": { 53 | "executor": "@nrwl/linter:eslint", 54 | "outputs": ["{options.outputFile}"], 55 | "options": { 56 | "lintFilePatterns": ["packages/core/**/*.ts"] 57 | } 58 | } 59 | }, 60 | "tags": [] 61 | } 62 | -------------------------------------------------------------------------------- /packages/core/release.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | const name = require('./package.json').name; 3 | const libraryFolderName = require('./project.json').name; 4 | const srcRoot = `packages/${libraryFolderName}`; 5 | 6 | module.exports = { 7 | extends: 'release.config.base.js', 8 | branches: [ 9 | 'main', 10 | 'next', 11 | { name: 'beta', prerelease: true }, 12 | { name: 'alpha', prerelease: true }, 13 | '+([0-9])?(.{+([0-9]),x}).x', 14 | ], 15 | pkgRoot: `dist/${srcRoot}`, 16 | tagFormat: name + '@${version}', 17 | commitPaths: [`${srcRoot}/*`], 18 | plugins: [ 19 | '@semantic-release/commit-analyzer', 20 | '@semantic-release/release-notes-generator', 21 | [ 22 | '@semantic-release/changelog', 23 | { 24 | changelogFile: `${srcRoot}/CHANGELOG.md`, 25 | }, 26 | ], 27 | '@semantic-release/npm', 28 | [ 29 | '@semantic-release/git', 30 | { 31 | assets: [`${srcRoot}/package.json`, `${srcRoot}/CHANGELOG.md`], 32 | message: 33 | `release(version): Release ${name} ` + 34 | '${nextRelease.version} [skip ci]\n\n${nextRelease.notes}', 35 | }, 36 | ], 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /packages/core/src/event/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export function initEventManager() { 3 | const eventsMap = new Map void>>(); 4 | 5 | return { 6 | listen(event: string, listener: (args: any) => void) { 7 | if (!eventsMap.has(event)) eventsMap.set(event, []); 8 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 9 | eventsMap.get(event)!.push(listener); 10 | 11 | return () => { 12 | eventsMap.set( 13 | event, 14 | (eventsMap.get(event) || []).filter((e) => e !== listener) 15 | ); 16 | }; 17 | }, 18 | fire(event: string, args?: any) { 19 | const events = eventsMap.get(event); 20 | 21 | if (events) { 22 | events.forEach((e) => e(args)); 23 | } 24 | }, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/graph/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './node'; 2 | -------------------------------------------------------------------------------- /packages/core/src/graph/components/node.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { PayloadAction } from '@reduxjs/toolkit'; 3 | import { Operation } from 'fast-json-patch'; 4 | 5 | import { FlavoredId, ID } from '../../utils'; 6 | 7 | export type NodeId = FlavoredId; 8 | 9 | type Checkpoint = { 10 | type: 'checkpoint'; 11 | val: State; 12 | }; 13 | 14 | type Patches = { 15 | type: 'patch'; 16 | checkpointRef: NodeId; 17 | val: Array; 18 | }; 19 | 20 | export type StateLike = Checkpoint | Patches; 21 | 22 | /** 23 | * Node Artifact Type 24 | */ 25 | 26 | /** 27 | * Artifact Types 28 | */ 29 | export type ArtifactId = FlavoredId; 30 | 31 | export type Artifact = { 32 | id: ArtifactId; 33 | createdOn: number; 34 | val: unknown; 35 | }; 36 | 37 | export type NodeArtifact = Array; 38 | 39 | /** 40 | * Node Metadata Type 41 | */ 42 | export type MetadataId = FlavoredId; 43 | 44 | export type Metadata = { 45 | id: MetadataId; 46 | type: string; 47 | createdOn: number; 48 | val: T; 49 | }; 50 | 51 | type NodeMetadata = { 52 | annotation: Array>; 53 | bookmark: Array>; 54 | [key: string]: Array>; 55 | }; 56 | 57 | /** 58 | * Node Types 59 | */ 60 | type BaseNode = { 61 | label: string; 62 | id: NodeId; 63 | createdOn: number; 64 | artifacts: NodeArtifact; 65 | meta: NodeMetadata; 66 | children: NodeId[]; 67 | state: StateLike; 68 | level: number; 69 | }; 70 | 71 | export type RootNode = BaseNode & { event: 'Root' }; 72 | 73 | export type SideEffects = { 74 | do: Array>; 75 | undo: Array>; 76 | }; 77 | 78 | export type StateNode = BaseNode & { 79 | event: Event; 80 | parent: NodeId; 81 | sideEffects: SideEffects; 82 | }; 83 | 84 | export type ProvenanceNode = 85 | | RootNode 86 | | StateNode; 87 | 88 | export type Nodes = Record< 89 | string, 90 | ProvenanceNode 91 | >; 92 | 93 | export function isStateNode( 94 | node: ProvenanceNode 95 | ): node is StateNode { 96 | return 'parent' in node; 97 | } 98 | 99 | export function isRootNode( 100 | node: ProvenanceNode 101 | ): node is RootNode { 102 | return !isStateNode(node); 103 | } 104 | 105 | export function createRootNode(args: { 106 | state: State; 107 | initialMetadata?: Record; 108 | initialArtifact?: unknown; 109 | label?: string; 110 | }): RootNode { 111 | const { label = undefined, state, initialArtifact, initialMetadata } = args; 112 | 113 | const commonMetadata: NodeMetadata = { 114 | annotation: [], 115 | bookmark: [], 116 | }; 117 | 118 | const meta = Object.keys(initialMetadata || {}).reduce( 119 | (acc: NodeMetadata, key) => { 120 | acc[key] = []; 121 | if (initialMetadata && initialMetadata[key]) { 122 | acc[key].push({ 123 | type: key, 124 | id: ID.get(), 125 | val: initialMetadata[key], 126 | createdOn: Date.now(), 127 | }); 128 | } 129 | return acc; 130 | }, 131 | commonMetadata 132 | ); 133 | 134 | const artifacts = initialArtifact 135 | ? [ 136 | { 137 | id: ID.get(), 138 | createdOn: Date.now(), 139 | val: initialArtifact, 140 | }, 141 | ] 142 | : []; 143 | 144 | return { 145 | id: ID.get(), 146 | label: label || 'Root', 147 | event: 'Root', 148 | children: [], 149 | level: 0, 150 | createdOn: Date.now(), 151 | meta, 152 | artifacts, 153 | state: { 154 | type: 'checkpoint', 155 | val: state, 156 | }, 157 | }; 158 | } 159 | 160 | export function createStateNode({ 161 | parent, 162 | state, 163 | label, 164 | sideEffects = { 165 | do: [], 166 | undo: [], 167 | }, 168 | initialMetadata, 169 | initialArtifact, 170 | event, 171 | }: { 172 | parent: ProvenanceNode; 173 | state: StateLike; 174 | initialMetadata?: Record; 175 | initialArtifact?: unknown; 176 | label: string; 177 | sideEffects?: SideEffects; 178 | event: Event; 179 | }): StateNode { 180 | const commonMetadata: NodeMetadata = { 181 | annotation: [], 182 | bookmark: [], 183 | }; 184 | 185 | const meta = Object.keys(initialMetadata || {}).reduce( 186 | (acc: NodeMetadata, key) => { 187 | acc[key] = []; 188 | if (initialMetadata && initialMetadata[key]) { 189 | acc[key].push({ 190 | type: key, 191 | id: ID.get(), 192 | val: initialMetadata[key], 193 | createdOn: Date.now(), 194 | }); 195 | } 196 | return acc; 197 | }, 198 | commonMetadata 199 | ); 200 | 201 | const artifacts = initialArtifact 202 | ? [ 203 | { 204 | id: ID.get(), 205 | createdOn: Date.now(), 206 | val: initialArtifact, 207 | }, 208 | ] 209 | : []; 210 | 211 | return { 212 | id: ID.get(), 213 | label, 214 | event, 215 | children: [], 216 | parent: parent.id, 217 | createdOn: Date.now(), 218 | meta, 219 | artifacts, 220 | sideEffects, 221 | state, 222 | level: parent.level + 1, 223 | }; 224 | } 225 | -------------------------------------------------------------------------------- /packages/core/src/graph/graph-slice.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { createSlice, PayloadAction, Slice } from '@reduxjs/toolkit'; 3 | 4 | import { castDraft } from 'immer'; 5 | import { ID } from '../utils'; 6 | import { createRootNode, NodeId, Nodes, StateNode } from './components'; 7 | 8 | type AddMetadataPayload = { 9 | id: NodeId; 10 | meta: Record; 11 | }; 12 | 13 | type AddArtifactPayload = { 14 | id: NodeId; 15 | artifact: unknown; 16 | }; 17 | 18 | export type ProvenanceGraph = { 19 | nodes: Nodes; 20 | current: NodeId; 21 | root: NodeId; 22 | }; 23 | 24 | type GraphSlice = Slice>; 25 | 26 | export type ProvenanceGraphActions = GraphSlice< 27 | S, 28 | E 29 | >['actions']; 30 | 31 | // Maybe swithc to reduxtoolkit createEntityAdapter 32 | 33 | export function graphSliceCreator( 34 | initialState: State, 35 | args: { 36 | artifact?: unknown; 37 | metadata?: Record; 38 | rootLabel?: string; 39 | } = {} 40 | ) { 41 | const { 42 | artifact = undefined, 43 | metadata = undefined, 44 | rootLabel = 'Root', 45 | } = args; 46 | 47 | const root = createRootNode({ 48 | state: initialState, 49 | label: rootLabel, 50 | initialArtifact: artifact, 51 | initialMetadata: metadata, 52 | }); 53 | 54 | const graph: ProvenanceGraph = { 55 | nodes: { 56 | [root.id]: root, 57 | }, 58 | root: root.id, 59 | current: root.id, 60 | }; 61 | 62 | const slice = createSlice({ 63 | name: 'provenance-graph', 64 | initialState: graph, 65 | reducers: { 66 | addMetadata(g, action: PayloadAction) { 67 | const { id, meta } = action.payload; 68 | const existingMetadata = g.nodes[id].meta; 69 | 70 | const metaData = Object.keys(meta).reduce((acc, key) => { 71 | if (!acc[key]) { 72 | acc[key] = []; 73 | } 74 | acc[key].push({ 75 | type: key, 76 | id: ID.get(), 77 | val: meta[key], 78 | createdOn: Date.now(), 79 | }); 80 | 81 | return acc; 82 | }, existingMetadata); 83 | 84 | g.nodes[action.payload.id].meta = metaData; 85 | }, 86 | addArtifact(g, action: PayloadAction) { 87 | g.nodes[action.payload.id].artifacts.push({ 88 | id: ID.get(), 89 | createdOn: Date.now(), 90 | val: castDraft(action.payload.artifact), 91 | }); 92 | }, 93 | changeCurrent(g, action: PayloadAction) { 94 | g.current = action.payload; 95 | }, 96 | addNode(g, { payload }: PayloadAction>) { 97 | g.nodes[payload.id] = payload as any; 98 | g.nodes[payload.parent].children.push(payload.id); 99 | g.current = payload.id; 100 | }, 101 | load(_, { payload }: PayloadAction>) { 102 | return payload; 103 | }, 104 | }, 105 | }); 106 | 107 | return slice; 108 | } 109 | -------------------------------------------------------------------------------- /packages/core/src/graph/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './provenance-graph'; 3 | -------------------------------------------------------------------------------- /packages/core/src/graph/provenance-graph.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { 3 | configureStore, 4 | createListenerMiddleware, 5 | isAnyOf, 6 | } from '@reduxjs/toolkit'; 7 | import { ID } from '../utils'; 8 | 9 | import { RootNode } from './components'; 10 | import { graphSliceCreator } from './graph-slice'; 11 | 12 | export type Trigger = 'traversal' | 'new'; 13 | 14 | export type CurrentChangeHandler = (trigger?: Trigger) => void; 15 | export type CurrentChangeHandlerConfig = { 16 | skipOnNew: boolean; 17 | }; 18 | export type UnsubscribeCurrentChangeListener = () => boolean; 19 | 20 | export type ProvenanceGraphStore = ReturnType; 21 | 22 | const f = () => initializeProvenanceGraph({}); 23 | 24 | export function initializeProvenanceGraph( 25 | initialState: State 26 | ) { 27 | const listeners: Map< 28 | string, 29 | { 30 | id: string; 31 | func: CurrentChangeHandler; 32 | config: CurrentChangeHandlerConfig; 33 | } 34 | > = new Map(); 35 | 36 | const { reducer, actions, getInitialState } = graphSliceCreator< 37 | State, 38 | Event 39 | >(initialState); 40 | 41 | const listenerMiddleware = createListenerMiddleware(); 42 | 43 | listenerMiddleware.startListening({ 44 | matcher: isAnyOf(actions.changeCurrent, actions.addNode), 45 | effect: (action, listenerApi) => { 46 | listenerApi.cancelActiveListeners(); 47 | listeners.forEach((listener) => { 48 | const isNew = isAnyOf(actions.addNode)(action); 49 | const { skipOnNew } = listener.config; 50 | 51 | if (skipOnNew && isNew) return; 52 | 53 | listener.func(isNew ? 'new' : 'traversal'); 54 | }); 55 | }, 56 | }); 57 | 58 | const store = configureStore({ 59 | reducer: reducer, 60 | middleware: (getDefaultMiddleware) => 61 | getDefaultMiddleware().prepend(listenerMiddleware.middleware), 62 | }); 63 | 64 | return { 65 | initialState: getInitialState(), 66 | get backend() { 67 | return store.getState(); 68 | }, 69 | get current() { 70 | return store.getState().nodes[store.getState().current]; 71 | }, 72 | get root() { 73 | return store.getState().nodes[ 74 | store.getState().root 75 | ] as RootNode; 76 | }, 77 | currentChange( 78 | func: CurrentChangeHandler, 79 | config: CurrentChangeHandlerConfig 80 | ): UnsubscribeCurrentChangeListener { 81 | const listener = { 82 | id: ID.get(), 83 | func, 84 | config, 85 | }; 86 | listeners.set(listener.id, listener); 87 | 88 | return () => listeners.delete(listener.id); 89 | }, 90 | update: store.dispatch, 91 | ...actions, 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export { createAction } from '@reduxjs/toolkit'; 2 | export * from './event'; 3 | export * from './graph'; 4 | export * from './provenance'; 5 | export * from './registry'; 6 | export * from './utils'; 7 | 8 | // Exports 9 | -------------------------------------------------------------------------------- /packages/core/src/provenance/index.ts: -------------------------------------------------------------------------------- 1 | export * from './trrack'; 2 | export * from './trrack-config-opts'; 3 | export * from './trrack-events'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/core/src/provenance/trrack-config-opts.ts: -------------------------------------------------------------------------------- 1 | import { Registry } from '../registry/reg'; 2 | 3 | export type ConfigureTrrackOptions = { 4 | registry: Registry; 5 | initialState: S; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/core/src/provenance/trrack-events.ts: -------------------------------------------------------------------------------- 1 | export enum TrrackEvents { 2 | TRAVERSAL_START = 'Traversal_Start', 3 | TRAVERSAL_END = 'Traversal_End', 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/src/provenance/types.ts: -------------------------------------------------------------------------------- 1 | import { PayloadAction } from '@reduxjs/toolkit'; 2 | import { NodeId } from '@trrack/core'; 3 | import { 4 | Artifact, 5 | CurrentChangeHandler, 6 | Metadata, 7 | ProvenanceGraphStore, 8 | ProvenanceNode, 9 | RootNode, 10 | SideEffects, 11 | UnsubscribeCurrentChangeListener, 12 | } from '../graph'; 13 | import { ProvenanceGraph } from '../graph/graph-slice'; 14 | import { Registry } from '../registry'; 15 | import { TrrackEvents } from './trrack-events'; 16 | 17 | export type RecordActionArgs = { 18 | label: string; 19 | state: State; 20 | eventType: Event; 21 | sideEffects: SideEffects; 22 | onlySideEffects?: boolean; 23 | }; 24 | 25 | export interface Trrack { 26 | registry: Registry; 27 | isTraversing: boolean; 28 | getState(node?: ProvenanceNode): State; 29 | graph: ProvenanceGraphStore; 30 | current: ProvenanceNode; 31 | root: RootNode; 32 | record(args: RecordActionArgs): void; 33 | apply( 34 | label: string, 35 | act: PayloadAction 36 | ): any; 37 | to(node: NodeId): Promise; 38 | metadata: { 39 | add(metadata: Record, node?: NodeId): void; 40 | latestOfType( 41 | type: string, 42 | node?: NodeId 43 | ): Metadata | undefined; 44 | allOfType( 45 | type: string, 46 | node?: NodeId 47 | ): Metadata[] | undefined; 48 | latest(node?: NodeId): Record | undefined; 49 | all(node?: NodeId): Record | undefined; 50 | types(node?: NodeId): string[]; 51 | }; 52 | artifact: { 53 | add(artifact: A, node?: NodeId): void; 54 | latest(node?: NodeId): Artifact | undefined; 55 | all(node?: NodeId): Artifact[] | undefined; 56 | }; 57 | annotations: { 58 | add(annotation: string, node?: NodeId): void; 59 | latest(node?: NodeId): string | undefined; 60 | all(node?: NodeId): string[] | undefined; 61 | }; 62 | bookmarks: { 63 | add(node?: NodeId): void; 64 | remove(node?: NodeId): void; 65 | is(node?: NodeId): boolean; 66 | toggle(node?: NodeId): void; 67 | }; 68 | undo(): Promise; 69 | redo(to?: 'latest' | 'oldest'): Promise; 70 | currentChange( 71 | listener: CurrentChangeHandler, 72 | skipOnNew?: boolean 73 | ): UnsubscribeCurrentChangeListener; 74 | done(): void; 75 | tree(): any; 76 | on(event: TrrackEvents, listener: (args?: any) => void): void; 77 | export(): string; 78 | exportObject(): ProvenanceGraph; 79 | import(graphString: string): void; 80 | importObject(graph: ProvenanceGraph): void; 81 | } 82 | -------------------------------------------------------------------------------- /packages/core/src/registry/action.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { PayloadAction } from '@reduxjs/toolkit'; 3 | 4 | export type TrrackActionFunction< 5 | DoActionType extends string, 6 | UndoActionType extends string, 7 | UndoActionPayload, 8 | DoActionPayload 9 | > = (args: DoActionPayload) => { 10 | do?: PayloadAction; 11 | undo: PayloadAction; 12 | }; 13 | 14 | export type ProduceWrappedStateChangeFunction = (state: T, args: any) => T 15 | 16 | export type StateChangeFunction = ( 17 | state: State, 18 | payload: Payload 19 | ) => ReturnType>; 20 | 21 | 22 | export type Label = string; 23 | export type LabelGenerator = (args: Args) => Label; 24 | 25 | export type TrrackActionConfig = { 26 | hasSideEffects: boolean; 27 | eventType: Event; 28 | label: LabelGenerator; 29 | }; 30 | 31 | export type TrrackActionRecord< 32 | DoActionType extends string, 33 | DoActionPayload, 34 | UndoActionType extends string, 35 | UndoActionPayload 36 | > = { 37 | do: PayloadAction; 38 | undo: PayloadAction; 39 | }; 40 | -------------------------------------------------------------------------------- /packages/core/src/registry/index.ts: -------------------------------------------------------------------------------- 1 | export * from './action'; 2 | export * from './reg'; 3 | -------------------------------------------------------------------------------- /packages/core/src/registry/reg.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { createAction } from '@reduxjs/toolkit'; 3 | import produce, { enablePatches } from 'immer'; 4 | 5 | import { 6 | Label, 7 | LabelGenerator, 8 | ProduceWrappedStateChangeFunction, 9 | StateChangeFunction, 10 | TrrackActionConfig, 11 | TrrackActionFunction, 12 | } from './action'; 13 | 14 | enablePatches(); 15 | 16 | 17 | type TrrackActionRegisteredObject = { 18 | func: TrrackActionFunction | ProduceWrappedStateChangeFunction; 19 | config: TrrackActionConfig; 20 | }; 21 | 22 | function prepareAction(action: TrrackActionFunction | StateChangeFunction) { 23 | return action.length === 2 ? produce(action) as unknown as ProduceWrappedStateChangeFunction : action as TrrackActionFunction; 24 | } 25 | 26 | export class Registry { 27 | static create(): Registry { 28 | return new Registry(); 29 | } 30 | 31 | private registry: Map; 32 | 33 | private constructor() { 34 | this.registry = new Map(); 35 | } 36 | 37 | has(name: string) { 38 | return this.registry.has(name); 39 | } 40 | 41 | register< 42 | DoActionType extends string, 43 | UndoActionType extends string, 44 | DoActionPayload = any, 45 | UndoActionPayload = any, 46 | State = any 47 | >( 48 | type: DoActionType, 49 | actionFunction: TrrackActionFunction< 50 | DoActionType, 51 | UndoActionType, 52 | UndoActionPayload, 53 | DoActionPayload 54 | > | StateChangeFunction, 55 | config?: { 56 | eventType: Event; 57 | label: Label | LabelGenerator 58 | } 59 | ) { 60 | const isState = actionFunction.length === 2; 61 | 62 | if (actionFunction.length > 2) 63 | throw new Error('Incorrect action function signature. Action function can only have two arguments at most!'); 64 | 65 | if (this.has(type)) throw new Error(`Already registered: ${type}`); 66 | 67 | const { label = type, eventType = type as unknown as Event } = config || {}; 68 | 69 | this.registry.set(type, { 70 | func: prepareAction(actionFunction), 71 | config: { 72 | hasSideEffects: !isState, 73 | label: 74 | typeof label === 'string' 75 | ? ((() => label) as LabelGenerator) 76 | : label, 77 | eventType, 78 | }, 79 | }); 80 | 81 | return createAction(type); 82 | } 83 | 84 | get(type: string) { 85 | const action = this.registry.get(type); 86 | 87 | if (!action) throw new Error(`Not registered: ${type}`); 88 | 89 | return action; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/core/src/utils/id.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | /** 4 | * Type to add branding flavor 5 | */ 6 | type Flavoring = { 7 | _type?: TFlavor; 8 | }; 9 | 10 | //'https://spin.atomicobject.com/2018/01/15/typescript-flexible-nominal-typing/ 11 | export type FlavoredId = TBaseId & Flavoring; 12 | 13 | export class ID { 14 | private static ids: Map = new Map(); 15 | 16 | static get() { 17 | let id = uuid(); 18 | 19 | while (this.ids.has(id)) { 20 | id = uuid(); 21 | } 22 | 23 | this.ids.set(id, true); 24 | 25 | return id; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './id'; 2 | -------------------------------------------------------------------------------- /packages/core/tests/event.spec.ts: -------------------------------------------------------------------------------- 1 | import { initEventManager } from '../src/event/index'; 2 | describe('Event Manager', () => { 3 | it('should create an instance of EventManager', () => { 4 | const eventManager = initEventManager(); 5 | 6 | expect(eventManager).toBeDefined(); 7 | expect(eventManager).toHaveProperty('listen'); 8 | expect(typeof eventManager.listen).toBe('function'); 9 | 10 | expect(eventManager).toHaveProperty('fire'); 11 | expect(typeof eventManager.fire).toBe('function'); 12 | }); 13 | 14 | it('should attach event listener and trigger without arguments', () => { 15 | const eventManager = initEventManager(); 16 | 17 | const EVENT = 'test'; 18 | 19 | let count = 0; 20 | 21 | const handler = vi.fn(() => { 22 | count = count + 1; 23 | }); 24 | 25 | eventManager.listen(EVENT, handler); 26 | 27 | expect(handler).not.toHaveBeenCalled(); 28 | expect(count).toBe(0); 29 | 30 | eventManager.fire(EVENT); 31 | expect(handler).toHaveBeenCalledTimes(1); 32 | expect(count).toBe(1); 33 | 34 | eventManager.fire(EVENT); 35 | expect(handler).toHaveBeenCalledTimes(2); 36 | expect(count).toBe(2); 37 | }); 38 | 39 | it('should attach event listener and trigger with arguments', () => { 40 | const eventManager = initEventManager(); 41 | 42 | const EVENT = 'test'; 43 | 44 | const arr: string[] = []; 45 | 46 | const ARG1 = 'Test 1'; 47 | const ARG2 = 'Test 2'; 48 | 49 | const handler = vi.fn((toAdd: string) => { 50 | arr.push(toAdd); 51 | }); 52 | 53 | eventManager.listen(EVENT, handler); 54 | 55 | expect(handler).not.toHaveBeenCalled(); 56 | expect(arr.length).toBe(0); 57 | 58 | eventManager.fire(EVENT, ARG1); 59 | expect(handler).toHaveBeenCalledTimes(1); 60 | expect(arr.length).toBe(1); 61 | expect(arr).toContain(ARG1); 62 | 63 | eventManager.fire(EVENT, ARG2); 64 | expect(handler).toHaveBeenCalledTimes(2); 65 | expect(arr.length).toBe(2); 66 | expect(arr).toContain(ARG2); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /packages/core/tests/import_export.test.ts: -------------------------------------------------------------------------------- 1 | import { initializeTrrack } from '../src/provenance/trrack'; 2 | import { Registry } from '../src/registry'; 3 | 4 | function setup() { 5 | const registry = Registry.create(); 6 | 7 | const add = registry.register('add', (state, amt) => { 8 | state.counter += amt; 9 | }); 10 | 11 | const sub = registry.register('sub', (state, amt) => { 12 | state.counter -= amt; 13 | }); 14 | 15 | const trrack = initializeTrrack({ 16 | registry, 17 | initialState: { 18 | counter: 0, 19 | }, 20 | }); 21 | 22 | return { trrack, add, sub }; 23 | } 24 | 25 | describe('Export', () => { 26 | it('export functions should return a stringified trrack graph and graph object', () => { 27 | const { trrack, add } = setup(); 28 | 29 | expect(trrack.getState().counter).toEqual(0); 30 | 31 | trrack.apply('Add', add(2)); 32 | expect(trrack.getState().counter).toEqual(2); 33 | 34 | const exportStr = trrack.export(); 35 | expect(exportStr).toBeTypeOf('string'); 36 | 37 | const parsed = JSON.parse(exportStr); 38 | expect(parsed).toHaveProperty('nodes'); 39 | expect(parsed).toHaveProperty('current'); 40 | expect(parsed).toHaveProperty('root'); 41 | }); 42 | 43 | it('import functions should load a stringified trrack graph and graph object', () => { 44 | const { trrack, add } = setup(); 45 | 46 | expect(trrack.getState().counter).toEqual(0); 47 | 48 | trrack.apply('Add', add(2)); 49 | expect(trrack.getState().counter).toEqual(2); 50 | 51 | const exportStr = trrack.export(); 52 | const exportObject = trrack.exportObject(); 53 | 54 | const { trrack: trrackStr } = setup(); 55 | const { trrack: trrackObj } = setup(); 56 | 57 | trrackStr.import(exportStr); 58 | trrackObj.importObject(exportObject); 59 | 60 | expect(trrackStr.getState().counter).toEqual(trrack.getState().counter); 61 | expect(trrackStr.root.id).toEqual(trrack.root.id); 62 | expect(trrackStr.current.id).toEqual(trrack.current.id); 63 | expect(Object.keys(trrackStr.graph.backend).length).toEqual( 64 | Object.keys(trrack.graph.backend).length 65 | ); 66 | 67 | expect(trrackObj.getState().counter).toEqual(trrack.getState().counter); 68 | expect(trrackObj.root.id).toEqual(trrack.root.id); 69 | expect(trrackObj.current.id).toEqual(trrack.current.id); 70 | expect(Object.keys(trrackObj.graph.backend).length).toEqual( 71 | Object.keys(trrack.graph.backend).length 72 | ); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/core/tests/node.spec.ts: -------------------------------------------------------------------------------- 1 | describe('Placeholder test', () => { 2 | it('should pass', () => { 3 | expect(true).toBeTruthy(); 4 | }); 5 | }); 6 | 7 | // describe('Provenance Node', () => { 8 | // it('should create root node', () => { 9 | // type State = { 10 | // test: number; 11 | // }; 12 | 13 | // const dummyState: State = { 14 | // test: 0, 15 | // }; 16 | 17 | // const node = createRootNode({ 18 | // state: dummyState, 19 | // artifact: { 20 | // createdOn: Date.now(), 21 | // id: 'Test Artifact', 22 | // val: {}, 23 | // }, 24 | // }); 25 | 26 | // expect(node).toBeDefined(); 27 | // expect(node).toMatchObject({ 28 | // id: expect.any(String), 29 | // label: 'Root', 30 | // level: 0, 31 | // meta: expect.objectContaining({ 32 | // createdOn: expect.any(Number), 33 | // eventType: 'Root', 34 | // }), 35 | // state: expect.objectContaining({ 36 | // type: 'checkpoint', 37 | // val: dummyState, 38 | // }), 39 | // artifact: expect.objectContaining({ 40 | // createdOn: expect.any(Number), 41 | // id: expect.any(String), 42 | // val: expect.any(Object), 43 | // }), 44 | // }); 45 | // }); 46 | 47 | // it('should create state node', () => { 48 | // type State = { 49 | // test: number; 50 | // }; 51 | 52 | // const dummyState: State = { 53 | // test: 0, 54 | // }; 55 | 56 | // const root = createRootNode({ 57 | // state: dummyState, 58 | // artifact: { 59 | // createdOn: Date.now(), 60 | // id: 'Test Artifact', 61 | // val: {}, 62 | // }, 63 | // }); 64 | 65 | // const state = createStateNode({ 66 | // parent: root, 67 | // state: { type: 'checkpoint', val: { ...dummyState, test: 1 } }, 68 | // label: 'Test Label', 69 | // eventType: 'Test', 70 | // }); 71 | 72 | // expect(state).toBeDefined(); 73 | // expect(state).toMatchObject({ 74 | // id: expect.any(String), 75 | // label: 'Root', 76 | // level: 0, 77 | // meta: expect.objectContaining({ 78 | // createdOn: expect.any(Number), 79 | // eventType: 'Root', 80 | // }), 81 | // state: expect.objectContaining({ 82 | // type: 'checkpoint', 83 | // val: dummyState, 84 | // }), 85 | // artifact: undefined, 86 | // }); 87 | // }); 88 | // }); 89 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "types": ["vitest", "vitest/globals"] 12 | }, 13 | "files": [], 14 | "include": [], 15 | "references": [ 16 | { 17 | "path": "./tsconfig.lib.json" 18 | }, 19 | { 20 | "path": "./tsconfig.spec.json" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["vitest/globals", "node"] 6 | }, 7 | "include": [ 8 | "vite.config.ts", 9 | "tests/**/*.test.ts", 10 | "tests**/*.spec.ts", 11 | "tests/**/*.test.tsx", 12 | "tests/**/*.spec.tsx", 13 | "tests/**/*.test.js", 14 | "tests/**/*.spec.js", 15 | "tests/**/*.test.jsx", 16 | "tests/**/*.spec.jsx", 17 | "tests/**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | 4 | import { join } from 'path'; 5 | import dts from 'vite-plugin-dts'; 6 | import viteTsConfigPaths from 'vite-tsconfig-paths'; 7 | 8 | export default defineConfig({ 9 | cacheDir: '../../node_modules/.vite/core', 10 | define: { 11 | 'process.env': {}, 12 | }, 13 | plugins: [ 14 | dts({ 15 | tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), 16 | // Faster builds by skipping tests. Set this to false to enable type checking. 17 | skipDiagnostics: true, 18 | }), 19 | 20 | viteTsConfigPaths({ 21 | root: '../../', 22 | }), 23 | ], 24 | 25 | // Uncomment this if you are using workers. 26 | // worker: { 27 | // plugins: [ 28 | // viteTsConfigPaths({ 29 | // root: '../../', 30 | // }), 31 | // ], 32 | // }, 33 | 34 | // Configuration for building your library. 35 | // See: https://vitejs.dev/guide/build.html#library-mode 36 | build: { 37 | sourcemap: true, 38 | lib: { 39 | // Could also be a dictionary or array of multiple entry points. 40 | entry: 'src/index.ts', 41 | fileName: 'index', 42 | 43 | // UMD name 44 | name: 'Trrack', 45 | // Change this to the formats you want to support. 46 | // Don't forgot to update your package.json as well. 47 | formats: ['es', 'cjs', 'umd'], 48 | }, 49 | rollupOptions: { 50 | // External packages that should not be bundled into your library. 51 | external: ['@reduxjs/toolkit'], 52 | output: { 53 | globals: { 54 | '@reduxjs/toolkit': 'RTK', 55 | }, 56 | }, 57 | }, 58 | }, 59 | 60 | test: { 61 | globals: true, 62 | cache: { 63 | dir: '../../node_modules/.vitest', 64 | }, 65 | environment: 'jsdom', 66 | include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /packages/redux/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/js/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/redux/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /packages/redux/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/redux/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0-beta.1 (2023-03-07) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * renaming sideEffect reducers ([#11](https://github.com/Trrack/trrackjs/issues/11)) ([cf7c787](https://github.com/Trrack/trrackjs/commit/cf7c787331b27c25de91201449ab16ccea5a36f9)) 7 | * 🐛 Handled double rendering in useEffect ([eccc4c1](https://github.com/Trrack/trrackjs/commit/eccc4c11a296e2dd0d77f7c10c9ead04b48831c8)) 8 | * Fixed the redux async execution ([36c3b9d](https://github.com/Trrack/trrackjs/commit/36c3b9d40e33b0b2d1d13265bac80997184e890e)) 9 | * **redux:** fixed export in redux package.json ([add8e97](https://github.com/Trrack/trrackjs/commit/add8e970d5b64b951977e6b505224fbf00c3db6c)) 10 | 11 | 12 | ### Features 13 | 14 | * Updating sideEffectReducer ([#12](https://github.com/Trrack/trrackjs/issues/12)) ([5b138f2](https://github.com/Trrack/trrackjs/commit/5b138f2a9cdad18e61933d2390c51ee656f0f709)) 15 | * 🎸 Fixed import and export ([2ce70eb](https://github.com/Trrack/trrackjs/commit/2ce70eb8c0be115771ee7f6dc7c8730e21366b51)) 16 | * 🎸 Moved to nx monorepo for latest build support ([de91c39](https://github.com/Trrack/trrackjs/commit/de91c39d649d2442df12bbd06b40b5b274f961f9)) 17 | * 🎸 Updating sideEffectReducer ([#10](https://github.com/Trrack/trrackjs/issues/10)) ([b51a32f](https://github.com/Trrack/trrackjs/commit/b51a32f8f86da27f1e1074a5d65b39970299d205)) 18 | * guitar Added API for handling thunk actions with createAsync ([a2ecf38](https://github.com/Trrack/trrackjs/commit/a2ecf381aba0b9964bea1593761bc061187e7a9d)) 19 | 20 | # Changelog 21 | 22 | This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). 23 | 24 | ### [0.0.6](https://github.com/Trrack/trrackjs/compare/redux-0.0.5...redux-0.0.6) (2022-08-12) 25 | 26 | 27 | ### Features 28 | 29 | * 🎸 Fixed import and export ([2ce70eb](https://github.com/Trrack/trrackjs/commit/2ce70eb8c0be115771ee7f6dc7c8730e21366b51)) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * 🐛 Handled double rendering in useEffect ([eccc4c1](https://github.com/Trrack/trrackjs/commit/eccc4c11a296e2dd0d77f7c10c9ead04b48831c8)) 35 | 36 | ### [0.0.5](https://github.com/Trrack/trrackjs/compare/redux-0.0.4...redux-0.0.5) (2022-08-11) 37 | 38 | ### [0.0.4](https://github.com/Trrack/trrackjs/compare/redux-0.0.3...redux-0.0.4) (2022-08-04) 39 | 40 | 41 | ### Features 42 | 43 | * Updating sideEffectReducer ([#12](https://github.com/Trrack/trrackjs/issues/12)) ([5b138f2](https://github.com/Trrack/trrackjs/commit/5b138f2a9cdad18e61933d2390c51ee656f0f709)) 44 | * guitar Added API for handling thunk actions with createAsync ([a2ecf38](https://github.com/Trrack/trrackjs/commit/a2ecf381aba0b9964bea1593761bc061187e7a9d)) 45 | 46 | ### [0.0.3](https://github.com/Trrack/trrackjs/compare/redux-0.0.2...redux-0.0.3) (2022-08-02) 47 | 48 | ### 0.0.1 (2022-08-02) 49 | 50 | 51 | ### Features 52 | 53 | * 🎸 Moved to nx monorepo for latest build support ([de91c39](https://github.com/Trrack/trrackjs/commit/de91c39d649d2442df12bbd06b40b5b274f961f9)) 54 | * 🎸 Updating sideEffectReducer ([#10](https://github.com/Trrack/trrackjs/issues/10)) ([b51a32f](https://github.com/Trrack/trrackjs/commit/b51a32f8f86da27f1e1074a5d65b39970299d205)) 55 | 56 | 57 | ### Bug Fixes 58 | 59 | * Fixed the redux async execution ([36c3b9d](https://github.com/Trrack/trrackjs/commit/36c3b9d40e33b0b2d1d13265bac80997184e890e)) 60 | 61 | ### [0.0.2](https://github.com/Trrack/trrackjs/compare/redux-0.0.1...redux-0.0.2) (2022-08-01) 62 | 63 | ### [0.0.1](https://github.com/Trrack/trrackjs/compare/redux-0.0.1-0...redux-0.0.1) (2022-08-01) 64 | 65 | ### [0.0.1-0](https://github.com/Trrack/trrackjs/compare/redux-0.0.1-alpha.0...redux-0.0.1-0) (2022-08-01) 66 | 67 | ### 0.0.1-alpha.0 (2022-08-01) 68 | 69 | 70 | ### Features 71 | 72 | * 🎸 Moved to nx monorepo for latest build support ([de91c39](https://github.com/Trrack/trrackjs/commit/de91c39d649d2442df12bbd06b40b5b274f961f9)) 73 | * 🎸 Preparing for release ([a0209fd](https://github.com/Trrack/trrackjs/commit/a0209fd41d0cb2b8d1d9a530de9e4dc79af3da71)) 74 | -------------------------------------------------------------------------------- /packages/redux/README.md: -------------------------------------------------------------------------------- 1 | # redux 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build redux` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test redux` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /packages/redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trrack/redux", 3 | "version": "0.0.0-semantic-release", 4 | "main": "./index.js", 5 | "types": "./index.d.ts", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Trrack/trrackjs.git" 9 | }, 10 | "dependencies": { 11 | "@reduxjs/toolkit": "^1.9.1" 12 | }, 13 | "peerDependencies": { 14 | "@reduxjs/toolkit": "^1.9.1", 15 | "@trrack/core": "^1.0.0" 16 | }, 17 | "exports": { 18 | ".": { 19 | "import": "./index.mjs", 20 | "require": "./index.js" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/redux/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/redux/src", 5 | "projectType": "library", 6 | "implicitDependencies": ["core"], 7 | "targets": { 8 | "build": { 9 | "executor": "@nrwl/vite:build", 10 | "outputs": ["{options.outputPath}"], 11 | "defaultConfiguration": "production", 12 | "options": { 13 | "outputPath": "dist/packages/redux" 14 | }, 15 | "configurations": { 16 | "development": { 17 | "mode": "development" 18 | }, 19 | "production": { 20 | "mode": "production" 21 | } 22 | } 23 | }, 24 | "release": { 25 | "executor": "nx:run-commands", 26 | "outputs": [], 27 | "options": { 28 | "command": "npx semantic-release-plus --extends ./packages/redux/release.config.js", 29 | "parallel": false 30 | } 31 | }, 32 | "publish": { 33 | "executor": "nx:run-commands", 34 | "options": { 35 | "command": "node tools/scripts/publish.mjs redux {args.ver} {args.tag}" 36 | }, 37 | "dependsOn": ["build"] 38 | }, 39 | "test": { 40 | "executor": "@nrwl/vite:test", 41 | "outputs": ["{workspaceRoot}/coverage/packages/redux"], 42 | "options": { 43 | "passWithNoTests": true, 44 | "reportsDirectory": "../../coverage/packages/redux" 45 | } 46 | }, 47 | "lint": { 48 | "executor": "@nrwl/linter:eslint", 49 | "outputs": ["{options.outputFile}"], 50 | "options": { 51 | "lintFilePatterns": ["packages/redux/**/*.ts"] 52 | } 53 | } 54 | }, 55 | "tags": [] 56 | } 57 | -------------------------------------------------------------------------------- /packages/redux/release.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | const name = require('./package.json').name; 3 | const libraryFolderName = require('./project.json').name; 4 | const srcRoot = `packages/${libraryFolderName}`; 5 | 6 | module.exports = { 7 | extends: 'release.config.base.js', 8 | branches: [ 9 | 'main', 10 | 'next', 11 | { name: 'beta', prerelease: true }, 12 | { name: 'alpha', prerelease: true }, 13 | '+([0-9])?(.{+([0-9]),x}).x', 14 | ], 15 | pkgRoot: `dist/${srcRoot}`, 16 | tagFormat: name + '@${version}', 17 | commitPaths: [`${srcRoot}/*`], 18 | plugins: [ 19 | '@semantic-release/commit-analyzer', 20 | '@semantic-release/release-notes-generator', 21 | [ 22 | '@semantic-release/changelog', 23 | { 24 | changelogFile: `${srcRoot}/CHANGELOG.md`, 25 | }, 26 | ], 27 | '@semantic-release/npm', 28 | [ 29 | '@semantic-release/git', 30 | { 31 | assets: [`${srcRoot}/package.json`, `${srcRoot}/CHANGELOG.md`], 32 | message: 33 | `release(version): Release ${name} ` + 34 | '${nextRelease.version} [skip ci]\n\n${nextRelease.notes}', 35 | }, 36 | ], 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /packages/redux/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './slice'; 2 | export * from './store'; 3 | -------------------------------------------------------------------------------- /packages/redux/src/slice/index.ts: -------------------------------------------------------------------------------- 1 | export * from './trrackableSliceCreator'; 2 | export * from './types'; 3 | export * from './utils'; 4 | -------------------------------------------------------------------------------- /packages/redux/src/slice/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionCreatorWithPayload, 3 | AsyncThunk, 4 | CaseReducerActions, 5 | createAction, 6 | PayloadAction, 7 | PayloadActionCreator, 8 | Slice, 9 | SliceCaseReducers, 10 | } from '@reduxjs/toolkit'; 11 | import { Label, LabelGenerator } from '@trrack/core'; 12 | 13 | export type LabelLike> = Partial<{ 14 | [K in keyof CaseReducerActions]: CaseReducerActions< 15 | CaseReducers, 16 | any 17 | >[K] extends PayloadActionCreator 18 | ? Label | LabelGenerator

19 | : never; 20 | }>; 21 | 22 | export type LabelGenerators = { 23 | [key: string]: LabelGenerator; 24 | }; 25 | 26 | /** 27 | * Events 28 | */ 29 | export type ReducerEventTypes< 30 | Event extends string, 31 | CaseReducers extends SliceCaseReducers 32 | > = { 33 | [K in keyof CaseReducerActions]: Event; 34 | }; 35 | 36 | /** 37 | * Action Name and Type map 38 | */ 39 | 40 | export type ActionNameToTypeMap< 41 | CaseReducers extends SliceCaseReducers, 42 | CRA extends CaseReducerActions = CaseReducerActions< 43 | CaseReducers, 44 | any 45 | > 46 | > = { 47 | [K in keyof CRA]: CRA[K] extends ActionCreatorWithPayload 48 | ? T 49 | : never; 50 | }; 51 | 52 | /** 53 | * Do Undo Action Creators 54 | */ 55 | export const NO_OP_ACTION = createAction('NO_OP'); 56 | 57 | type NoOpActionType = typeof NO_OP_ACTION; 58 | 59 | type DoUndoActionCreator< 60 | Payload, 61 | DoPayload = Payload, 62 | UndoPayload = DoPayload 63 | > = (args: { 64 | action: PayloadAction; 65 | currentState: any; 66 | previousState: any; 67 | }) => { 68 | do?: NoOpActionType | PayloadAction; 69 | undo: NoOpActionType | PayloadAction; 70 | }; 71 | 72 | export type DoUndoActionCreators> = 73 | Partial<{ 74 | [key: string]: DoUndoActionCreator; 75 | // [K in keyof CaseReducerActions]: CaseReducerActions[K] extends PayloadActionCreator< 76 | // infer P 77 | // > 78 | // ? DoUndoActionCreator 79 | // : never; 80 | }>; 81 | 82 | export type GeneratedDoUndoActionCreators = { 83 | [key: string]: ( 84 | args: Parameters>[0] 85 | ) => Required>>; 86 | }; 87 | 88 | export const LABELS = Symbol('label'); 89 | export const DO_UNDO_ACTION_CREATORS = Symbol('do_undo_action_creators'); 90 | export const TRRACKABLE = Symbol('trrackable'); 91 | export const EVENTS = Symbol('events'); 92 | export const ACTION_NAME_TYPE_MAP = Symbol('action_name_type_map'); 93 | export const ASYNC_THUNKS = Symbol('async_thunks'); 94 | 95 | export type TrrackableSlice< 96 | State, 97 | CaseReducers extends SliceCaseReducers, 98 | Event extends string = string, 99 | Name extends string = string 100 | > = Slice & { 101 | [LABELS]: LabelGenerators; 102 | [EVENTS]: ReducerEventTypes; 103 | [TRRACKABLE]: boolean; 104 | [DO_UNDO_ACTION_CREATORS]: GeneratedDoUndoActionCreators; 105 | [ACTION_NAME_TYPE_MAP]: ActionNameToTypeMap; 106 | [ASYNC_THUNKS]: Array>; 107 | }; 108 | -------------------------------------------------------------------------------- /packages/redux/src/slice/utils.ts: -------------------------------------------------------------------------------- 1 | import { Slice } from '@reduxjs/toolkit'; 2 | 3 | import { TRRACKABLE, TrrackableSlice } from './types'; 4 | 5 | export function isSliceTrrackable( 6 | slice: Slice 7 | ): slice is TrrackableSlice { 8 | return TRRACKABLE ? TRRACKABLE in slice : false; 9 | } 10 | -------------------------------------------------------------------------------- /packages/redux/src/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from './trrackableStoreCreator'; 2 | export * from './types'; 3 | export * from './utils'; 4 | export * from './trrackStore'; 5 | -------------------------------------------------------------------------------- /packages/redux/src/store/trrackStore.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { NodeId } from '@trrack/core'; 3 | 4 | type TrrackSliceState = { 5 | current: NodeId; 6 | }; 7 | 8 | const initialState: TrrackSliceState = { 9 | current: null as any, 10 | }; 11 | 12 | const trrackSlice = createSlice({ 13 | name: 'trrack', 14 | initialState, 15 | reducers: { 16 | changeCurrent: (_, action: PayloadAction) => ({ 17 | current: action.payload, 18 | }), 19 | }, 20 | }); 21 | 22 | export const { changeCurrent } = trrackSlice.actions; 23 | 24 | export function getTrrackStore(init: TrrackSliceState) { 25 | return configureStore({ 26 | reducer: trrackSlice.reducer, 27 | preloadedState: init as any, 28 | }); 29 | } 30 | 31 | type TrrackStore = ReturnType; 32 | 33 | export type TrrackStoreType = ReturnType; 34 | -------------------------------------------------------------------------------- /packages/redux/src/store/types.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Slice } from '@reduxjs/toolkit'; 2 | import { CurriedGetDefaultMiddleware } from '@reduxjs/toolkit/dist/getDefaultMiddleware'; 3 | 4 | export type SliceMapLike = { 5 | [K in keyof State]: Slice; 6 | }; 7 | 8 | export type StateFromSliceMap = M extends SliceMapLike 9 | ? { [P in keyof M]: M[P] extends Slice ? S : never } 10 | : never; 11 | 12 | export type Middlewares = ReadonlyArray< 13 | Middleware, State> 14 | >; 15 | 16 | export type PossibleMiddleware< 17 | State = any, 18 | M extends Middlewares = Middlewares 19 | > = ((g: CurriedGetDefaultMiddleware) => M) | M; 20 | -------------------------------------------------------------------------------- /packages/redux/src/store/utils.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit'; 2 | 3 | import { Middlewares, PossibleMiddleware } from './types'; 4 | 5 | export function isMiddlewareArray( 6 | middleware: PossibleMiddleware 7 | ): middleware is Middlewares { 8 | return Array.isArray(middleware); 9 | } 10 | 11 | export function asyncDoUndoActionCreatorHelper(type: string, payload: T) { 12 | return createAction(type, function prepare(c: T) { 13 | return { 14 | payload: c, 15 | }; 16 | })(payload); 17 | } 18 | -------------------------------------------------------------------------------- /packages/redux/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; 2 | 3 | import { configureTrrackableStore, createTrrackableSlice } from '../src'; 4 | 5 | describe('it', () => { 6 | it('should work', () => { 7 | const getPostById = createAsyncThunk( 8 | 'test/getpostById', 9 | async (postId: number, _api) => { 10 | const response = await fetch( 11 | ` https://jsonplaceholder.typicode.com/posts/${postId} ` 12 | ); 13 | 14 | return response.json(); 15 | } 16 | ); 17 | 18 | const testSlice = createTrrackableSlice({ 19 | name: 'test', 20 | initialState: { 21 | hello: 'World', 22 | post: { 23 | userId: -1, 24 | id: -1, 25 | title: '', 26 | body: '', 27 | }, 28 | }, 29 | reducers: { 30 | sayHello(state, action: PayloadAction) { 31 | state.hello = action.payload; 32 | }, 33 | }, 34 | extraReducers: (builder) => { 35 | builder.addCase(getPostById.fulfilled, (state, action) => { 36 | state.post = action.payload; 37 | }); 38 | }, 39 | labels: { 40 | sayHello: (args) => `Say hello to ${args}`, 41 | }, 42 | doUndoActionCreators: { 43 | sayHello({ previousState }) { 44 | return { 45 | undo: sayHello(previousState.test.hello), 46 | }; 47 | }, 48 | }, 49 | }); 50 | 51 | const { sayHello } = testSlice.actions as any; 52 | 53 | const { store, trrack } = configureTrrackableStore({ 54 | reducer: { 55 | test: testSlice.reducer, 56 | }, 57 | slices: [testSlice], 58 | }); 59 | 60 | // store.dispatch(sayHello('Mars')); 61 | // trrack.undo(); 62 | 63 | expect(true).toBeTruthy(); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/redux/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "types": ["vitest", "vitest/globals"] 12 | }, 13 | "files": [], 14 | "include": [], 15 | "references": [ 16 | { 17 | "path": "./tsconfig.lib.json" 18 | }, 19 | { 20 | "path": "./tsconfig.spec.json" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/redux/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/redux/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["vitest/globals", "node"] 6 | }, 7 | "include": [ 8 | "vite.config.ts", 9 | "tests/**/*.test.ts", 10 | "tests**/*.spec.ts", 11 | "tests/**/*.test.tsx", 12 | "tests/**/*.spec.tsx", 13 | "tests/**/*.test.js", 14 | "tests/**/*.spec.js", 15 | "tests/**/*.test.jsx", 16 | "tests/**/*.spec.jsx", 17 | "tests/**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/redux/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | 4 | import { join } from 'path'; 5 | import dts from 'vite-plugin-dts'; 6 | import viteTsConfigPaths from 'vite-tsconfig-paths'; 7 | 8 | export default defineConfig({ 9 | cacheDir: '../../node_modules/.vite/redux', 10 | define: { 11 | 'process.env': {}, 12 | }, 13 | plugins: [ 14 | dts({ 15 | tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), 16 | // Faster builds by skipping tests. Set this to false to enable type checking. 17 | skipDiagnostics: true, 18 | }), 19 | 20 | viteTsConfigPaths({ 21 | root: '../../', 22 | }), 23 | ], 24 | 25 | // Uncomment this if you are using workers. 26 | // worker: { 27 | // plugins: [ 28 | // viteTsConfigPaths({ 29 | // root: '../../', 30 | // }), 31 | // ], 32 | // }, 33 | 34 | // Configuration for building your library. 35 | // See: https://vitejs.dev/guide/build.html#library-mode 36 | build: { 37 | sourcemap: true, 38 | lib: { 39 | // Could also be a dictionary or array of multiple entry points. 40 | entry: 'src/index.ts', 41 | fileName: 'index', 42 | 43 | // UMD name 44 | name: 'TrrackRTK', 45 | // Change this to the formats you want to support. 46 | // Don't forgot to update your package.json as well. 47 | formats: ['es', 'cjs', 'umd'], 48 | }, 49 | rollupOptions: { 50 | // External packages that should not be bundled into your library. 51 | external: ['@reduxjs/toolkit', '@trrack/core'], 52 | output: { 53 | globals: { 54 | '@reduxjs/toolkit': 'RTK', 55 | '@trrack/core': 'Trrack', 56 | }, 57 | }, 58 | }, 59 | }, 60 | 61 | test: { 62 | globals: true, 63 | cache: { 64 | dir: '../../node_modules/.vitest', 65 | }, 66 | environment: 'jsdom', 67 | include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /release.config.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'semantic-release-npm-github-publish', 3 | }; 4 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trrack/trrackjs/31af0ead8c2bd805e2cfe296b24b7376892db256/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/scripts/publish.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a minimal script to publish your package to "npm". 3 | * This is meant to be used as-is or customize as you see fit. 4 | * 5 | * This script is executed on "dist/path/to/library" as "cwd" by default. 6 | * 7 | * You might need to authenticate with NPM before running this script. 8 | */ 9 | 10 | import { readCachedProjectGraph } from '@nrwl/devkit'; 11 | import { execSync } from 'child_process'; 12 | import { readFileSync, writeFileSync } from 'fs'; 13 | import chalk from 'chalk'; 14 | 15 | function invariant(condition, message) { 16 | if (!condition) { 17 | console.error(chalk.bold.red(message)); 18 | process.exit(1); 19 | } 20 | } 21 | 22 | // Executing publish script: node path/to/publish.mjs {name} --version {version} --tag {tag} 23 | // Default "tag" to "next" so we won't publish the "latest" tag by accident. 24 | const [, , name, version, tag = 'next'] = process.argv; 25 | 26 | // A simple SemVer validation to validate the version 27 | const validVersion = /^\d+\.\d+\.\d(-\w+\.\d+)?/; 28 | invariant( 29 | version && validVersion.test(version), 30 | `No version provided or version did not match Semantic Versioning, expected: #.#.#-tag.# or #.#.#, got ${version}.` 31 | ); 32 | 33 | const graph = readCachedProjectGraph(); 34 | const project = graph.nodes[name]; 35 | 36 | invariant( 37 | project, 38 | `Could not find project "${name}" in the workspace. Is the project.json configured correctly?` 39 | ); 40 | 41 | const outputPath = project.data?.targets?.build?.options?.outputPath; 42 | invariant( 43 | outputPath, 44 | `Could not find "build.options.outputPath" of project "${name}". Is project.json configured correctly?` 45 | ); 46 | 47 | process.chdir(outputPath); 48 | 49 | // Updating the version in "package.json" before publishing 50 | try { 51 | const json = JSON.parse(readFileSync(`package.json`).toString()); 52 | json.version = version; 53 | writeFileSync(`package.json`, JSON.stringify(json, null, 2)); 54 | } catch (e) { 55 | console.error( 56 | chalk.bold.red(`Error reading package.json file from library build output.`) 57 | ); 58 | } 59 | 60 | // Execute "npm publish" to publish 61 | execSync(`npm publish --access public --tag ${tag}`); 62 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "importHelpers": true, 12 | "target": "es2015", 13 | "module": "esnext", 14 | "lib": ["es2017", "dom"], 15 | "skipLibCheck": true, 16 | "skipDefaultLibCheck": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@trrack/core": ["./packages/core/src/index.ts"], 20 | "react": ["node_modules/@types/react"] 21 | } 22 | }, 23 | "exclude": ["node_modules", "tmp"] 24 | } 25 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/", "destination": "/trrack" }] 3 | } 4 | --------------------------------------------------------------------------------