diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index a930b81ab2a4..35189ec48261 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -15,15 +15,14 @@ runs: with: node-version: 24.13.1 - - name: Restore node_modules cache - uses: actions/cache@v5 - id: node-modules-cache + # Restore-only: PRs read from main's cache but never save their own copy. + # This avoids duplicating ~1GB of yarn cache per PR branch. + - name: Restore yarn cache + uses: actions/cache/restore@v5 + id: yarn-cache with: path: | - node_modules - **/node_modules .yarn/cache - .yarn/unplugged .yarn/install-state.gz key: ${{ runner.os }}-node-24.13.1-yarn-${{ hashFiles('yarn.lock', '.yarnrc.yml', '.yarn/patches/*') }} restore-keys: | @@ -36,3 +35,14 @@ runs: # Hardened mode enables --check-resolutions on public PRs, adding ~15s. # See https://yarnpkg.com/features/security - Yarn recommends disabling in most jobs. YARN_ENABLE_HARDENED_MODE: 0 + + # Only save the cache on pushes to main to avoid per-PR duplicates. + # Runs after install so the cache is fully populated. + - name: Save yarn cache + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.yarn-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: | + .yarn/cache + .yarn/install-state.gz + key: ${{ runner.os }}-node-24.13.1-yarn-${{ hashFiles('yarn.lock', '.yarnrc.yml', '.yarn/patches/*') }} diff --git a/apps/examples/package.json b/apps/examples/package.json index 7c56206e989f..355e98380cbb 100644 --- a/apps/examples/package.json +++ b/apps/examples/package.json @@ -54,6 +54,7 @@ "ag-grid-react": "^32.3.3", "axe-core": "^4.10.3", "classnames": "^2.5.1", + "d3-geo": "^3.1.1", "lazyrepo": "0.0.0-alpha.27", "lodash": "^4.17.21", "pdf-lib": "^1.17.1", @@ -64,12 +65,17 @@ "react-helmet-async": "^1.3.0", "react-router-dom": "^6.28.2", "tldraw": "workspace:*", + "topojson-client": "^3.1.0", + "us-atlas": "^3.0.1", "vite": "^7.0.1" }, "devDependencies": { + "@types/d3-geo": "^3.1.0", "@types/lodash": "^4.17.14", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", + "@types/topojson-client": "^3.1.5", + "@types/topojson-specification": "^1.0.5", "@vitejs/plugin-react-swc": "^3.10.2", "dotenv": "^16.4.7", "remark": "^15.0.1", diff --git a/apps/examples/src/examples/use-cases/d3-map/D3MapExample.tsx b/apps/examples/src/examples/use-cases/d3-map/D3MapExample.tsx new file mode 100644 index 000000000000..06520819e392 --- /dev/null +++ b/apps/examples/src/examples/use-cases/d3-map/D3MapExample.tsx @@ -0,0 +1,78 @@ +import { Editor, TLComponents, Tldraw, useEditor } from 'tldraw' +import 'tldraw/tldraw.css' +import { UsMapShapeUtil } from './UsMapShapeUtil' +import { UsStateShapeUtil } from './UsStateShapeUtil' +import './d3-map.css' +import { MAP_HEIGHT, MAP_WIDTH } from './us-map-data' + +export const STATE_COLORS = [ + '#4e79a7', + '#f28e2b', + '#e15759', + '#76b7b2', + '#59a14f', + '#edc948', + '#b07aa1', + '#ff9da7', + '#9c755f', + '#bab0ac', + '#af7aa1', + '#d4a373', +] + +const shapeUtils = [UsMapShapeUtil, UsStateShapeUtil] + +function resetMap(editor: Editor) { + const allShapeIds = [...editor.getCurrentPageShapeIds()] + const mapAndStateIds = allShapeIds.filter((id) => { + const shape = editor.getShape(id) + return shape?.type === 'us-map' || shape?.type === 'us-state' + }) + if (mapAndStateIds.length > 0) { + editor.deleteShapes(mapAndStateIds) + } + editor.createShape({ + type: 'us-map', + x: 0, + y: 0, + props: { w: MAP_WIDTH, h: MAP_HEIGHT }, + }) + editor.zoomToFit({ animation: { duration: 200 } }) +} + +function TopPanel() { + const editor = useEditor() + return ( +