diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy-preview.yml similarity index 57% rename from .github/workflows/deploy.yml rename to .github/workflows/deploy-preview.yml index d116743..cd767d5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy-preview.yml @@ -1,10 +1,8 @@ # This workflow will do a clean install of node dependencies and deploy to AWS. -name: Deploy +name: Preview Deployment on: - push: - branches: [ main ] pull_request: branches: [ main ] @@ -16,24 +14,30 @@ jobs: build: name: Build runs-on: ubuntu-latest + outputs: + ariabaseurl: ${{ steps.vars.outputs.ariabaseurl }} + distdir: ${{ steps.vars.outputs.distdir }} + identifier: ${{ steps.vars.outputs.identifier }} steps: - name: Checkout 📥 uses: actions/checkout@v4 + - name: Setup vars 📋 + id: vars + run: | + echo "ariabaseurl=/aria/$(git rev-parse --short ${{ github.sha }})-20.x-dist" >> $GITHUB_OUTPUT + echo "distdir=$(git rev-parse --short ${{ github.sha }})-20.x-dist" >> $GITHUB_OUTPUT + echo "identifier=$(git rev-parse --short ${{ github.sha }})-20.x" >> $GITHUB_OUTPUT + - name: Use Node.js 20.12.0 ⚙️ uses: actions/setup-node@v4 with: node-version: 20 - - name: Setup vars 📋 - id: vars - run: | - echo ::set-output name=distdir::$(git rev-parse --short ${{ github.sha }})-20.x-dist - echo ::set-output name=ariabaseurl::/aria/$(git rev-parse --short ${{ github.sha }})-20.x-dist - - name: Install 🎯 run: cd site; npm install + - name: Build 🏃 run: cd site; npm run build env: @@ -46,12 +50,12 @@ jobs: if-no-files-found: error deploy: - name: Deploy + name: Deploy to Cloudfront runs-on: ubuntu-latest needs: build environment: name: preview - url: https://d13285jxgcxetl.cloudfront.net/aria/${{ steps.vars.outputs.distdir }}/index.html + url: https://d13285jxgcxetl.cloudfront.net/aria/${{ needs.build.outputs.identifier }}-aria.html steps: - name: Checkout 📥 @@ -70,30 +74,18 @@ jobs: role-duration-seconds: 900 # the ttl of the session, in seconds. aws-region: us-west-2 - - name: Setup vars 📋 - id: vars - run: | - echo ::set-output name=identifier::$(git rev-parse --short ${{ github.sha }})-20.x - echo ::set-output name=distdir::$(git rev-parse --short ${{ github.sha }})-20.x-dist - - - name: Prep files 🔨 - run: cd site ; npm run aws_deploy - env: - IDENTIFIER: ${{ steps.vars.outputs.identifier }} - DISTDIR: ${{ steps.vars.outputs.distdir }} - - name: Copy new index file to S3 📤 - run: aws s3 cp site/$DISTDIR/index.html s3://aria-site/aria/$IDENTIFIER-aria.html --no-progress + run: aws s3 cp site/dist/index.html s3://aria-site/aria/$IDENTIFIER-aria.html --no-progress env: - IDENTIFIER: ${{ steps.vars.outputs.identifier }} - DISTDIR: ${{ steps.vars.outputs.distdir }} + IDENTIFIER: ${{ needs.build.outputs.identifier }} + DISTDIR: ${{ needs.build.outputs.distdir }} - name: Copy new dist dir to S3 📤 - run: aws s3 cp site/$DISTDIR s3://aria-site/aria/$DISTDIR --recursive --no-progress + run: aws s3 cp site/dist s3://aria-site/aria/$DISTDIR --recursive --no-progress env: - IDENTIFIER: ${{ steps.vars.outputs.identifier }} - DISTDIR: ${{ steps.vars.outputs.distdir }} + IDENTIFIER: ${{ needs.build.outputs.identifier }} + DISTDIR: ${{ needs.build.outputs.distdir }} - name: Deployment complete! 🚀 run: | - echo "Your build is at: https://d13285jxgcxetl.cloudfront.net/aria/${{ steps.vars.outputs.distdir }}/index.html" >> $GITHUB_STEP_SUMMARY + echo "Your build is at: https://d13285jxgcxetl.cloudfront.net/aria/${{ needs.build.outputs.identifier }}-aria.html" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml new file mode 100644 index 0000000..680daf2 --- /dev/null +++ b/.github/workflows/deploy-production.yml @@ -0,0 +1,86 @@ +# This workflow will do a clean install of node dependencies and deploy to AWS. + +name: Production Deployment + +on: + push: + branches: [ main ] + +permissions: + id-token: write # required to use OIDC authentication + contents: read # required to checkout the code from the repo + +jobs: + build: + name: Build + runs-on: ubuntu-latest + outputs: + ariabaseurl: ${{ steps.vars.outputs.ariabaseurl }} + distdir: ${{ steps.vars.outputs.distdir }} + + steps: + - name: Checkout 📥 + uses: actions/checkout@v4 + + - name: Setup vars 📋 + id: vars + run: | + echo "ariabaseurl=/aria/$(git rev-parse --short ${{ github.sha }})-20.x-dist" >> $GITHUB_OUTPUT + echo "distdir=$(git rev-parse --short ${{ github.sha }})-20.x-dist" >> $GITHUB_OUTPUT + + - name: Use Node.js 20.12.0 ⚙️ + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install 🎯 + run: cd site; npm install + + - name: Build 🏃 + run: cd site; npm run build + env: + BASE_ARIA_URL: ${{ steps.vars.outputs.ariabaseurl }} + - name: Upload artifacts 📤 + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + path: site/dist + if-no-files-found: error + + deploy: + name: Deploy to Cloudfront + runs-on: ubuntu-latest + needs: build + environment: + name: preview + url: https://d13285jxgcxetl.cloudfront.net/aria/${{ needs.build.outputs.distdir }}/index.html + + steps: + - name: Checkout 📥 + uses: actions/checkout@v4 + + - name: Download artifacts 📥 + uses: actions/download-artifact@v4 + with: + name: build-artifacts + path: site/dist + + - name: Configure AWS credentials 🔐 + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::851725248912:role/aria-cicd-role + role-duration-seconds: 900 # the ttl of the session, in seconds. + aws-region: us-west-2 + + - name: Copy new index file to S3 📤 + run: aws s3 cp site/dist/index.html s3://aria-site/index.html --no-progress + env: + DISTDIR: ${{ needs.build.outputs.distdir }} + + - name: Copy new dist dir to S3 📤 + run: aws s3 cp site/dist s3://aria-site/aria/$DISTDIR --recursive --no-progress + env: + DISTDIR: ${{ needs.build.outputs.distdir }} + + - name: Invalidate Cloudfront cache 📤 + run: aws cloudfront create-invalidation --distribution-id E3GBPWSHZI8N0 --paths /index.html diff --git a/site/src/inspector_panel/InspectorPanel.jsx b/site/src/inspector_panel/InspectorPanel.jsx index a671731..ed93960 100644 --- a/site/src/inspector_panel/InspectorPanel.jsx +++ b/site/src/inspector_panel/InspectorPanel.jsx @@ -1,16 +1,10 @@ import PropTypes from "prop-types"; -import TableRow from "./TableRow"; import "./InspectorPanel.css"; -import CloseIcon from "@mui/icons-material/Close"; -import ThemePanel from "./ThemePanel"; -import { - BASE_TIPS, - BUILDING_TIPS, - DIVISION_TIPS, - PLACES_TIPS, - TRANSPORTATION_TIPS, - ADDRESSES_TIPS, -} from "./TipLibrary"; +import { getThemeConfig, isKnownTheme } from "./config/ThemeRegistry"; +import { processActiveFeature } from "./utils/EntityProcessor"; +import { extractPanelTitle } from "./utils/PanelTitleExtractor"; +import PanelHeader from "./components/PanelHeader"; +import FallbackTable from "./components/FallbackTable"; function InspectorPanel({ mode, @@ -24,156 +18,49 @@ function InspectorPanel({ return; } - const entity = { - theme: activeFeature.source, - type: activeFeature.sourceLayer, - ...activeFeature.properties, - }; - + // Process the active feature into an entity + const entity = processActiveFeature(activeFeature); const theme = entity["theme"]; - // Determine the panel title - use name if available, otherwise default - let panelTitle = "Inspector Panel"; - if (entity["names"]) { - try { - const names = JSON.parse(entity["names"]); - if (names["primary"]) { - panelTitle = names["primary"]; - } - } catch (e) { - // If parsing fails, keep default title - } - } + // Extract panel title + const panelTitle = extractPanelTitle(entity); - let inspectorPanel =
; + // Handle close panel + const handleClose = () => { + setFeatures([]); + setActiveFeature(null); + }; - if (theme === "base") { - inspectorPanel = ( - - ); - } else if (theme === "buildings") { - inspectorPanel = ( - - ); - } else if (theme === "divisions") { - inspectorPanel = ( - - ); - } else if (theme === "places") { - inspectorPanel = ( - - ); - } else if (theme === "transportation") { - inspectorPanel = ( - - ); - } else if (theme === "addresses") { + // Get theme configuration + const themeConfig = getThemeConfig(theme); + + let inspectorPanel; + + if (themeConfig) { + // Use the configured theme component + const ThemeComponent = themeConfig.component; inspectorPanel = ( - ); - } else if (theme === "addresses") { - inspectorPanel = ( - - ); } else { + // Fallback for unhandled themes console.log("unhandled theme type"); console.log(entity); - // Get all keys except those starting with @ - const allKeys = Object.keys(entity).filter((key) => !key.startsWith("@")); - - // Create custom ordering for class/subclass hierarchy - const orderedKeys = []; - const processedKeys = new Set(); - - // First pass: add all keys except subclass - allKeys.forEach(key => { - if (key !== "subclass") { - orderedKeys.push({ key, indented: false }); - processedKeys.add(key); - - // If this is "class" and "subclass" exists, add subclass right after - if (key === "class" && entity.hasOwnProperty("subclass")) { - orderedKeys.push({ key: "subclass", indented: true }); - processedKeys.add("subclass"); - } - } - }); - - // Second pass: add any remaining keys that weren't processed - allKeys.forEach(key => { - if (!processedKeys.has(key)) { - orderedKeys.push({ key, indented: false }); - } - }); - inspectorPanel = ( - - - {orderedKeys.map(({ key, indented }) => ( - - ))} - -
+ ); } return (
-
-
{panelTitle}
- -
+ {inspectorPanel}

+ + {orderedKeys.map(({ key, indented }) => ( + + ))} + + + ); +} + +FallbackTable.propTypes = { + mode: PropTypes.string, + entity: PropTypes.object.isRequired, +}; + +export default FallbackTable; diff --git a/site/src/inspector_panel/components/PanelHeader.jsx b/site/src/inspector_panel/components/PanelHeader.jsx new file mode 100644 index 0000000..7b7027b --- /dev/null +++ b/site/src/inspector_panel/components/PanelHeader.jsx @@ -0,0 +1,23 @@ +import PropTypes from "prop-types"; +import CloseIcon from "@mui/icons-material/Close"; + +function PanelHeader({ title, onClose }) { + return ( +

+ ); +} + +PanelHeader.propTypes = { + title: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, +}; + +export default PanelHeader; diff --git a/site/src/inspector_panel/config/ThemeRegistry.js b/site/src/inspector_panel/config/ThemeRegistry.js new file mode 100644 index 0000000..41c56dd --- /dev/null +++ b/site/src/inspector_panel/config/ThemeRegistry.js @@ -0,0 +1,44 @@ +import ThemePanel from "../ThemePanel"; +import { + BASE_TIPS, + BUILDING_TIPS, + DIVISION_TIPS, + PLACES_TIPS, + TRANSPORTATION_TIPS, + ADDRESSES_TIPS, +} from "../TipLibrary"; + +export const THEME_CONFIG = { + base: { + component: ThemePanel, + tips: BASE_TIPS, + }, + buildings: { + component: ThemePanel, + tips: BUILDING_TIPS, + }, + divisions: { + component: ThemePanel, + tips: DIVISION_TIPS, + }, + places: { + component: ThemePanel, + tips: PLACES_TIPS, + }, + transportation: { + component: ThemePanel, + tips: TRANSPORTATION_TIPS, + }, + addresses: { + component: ThemePanel, + tips: ADDRESSES_TIPS, + }, +}; + +export const getThemeConfig = (theme) => { + return THEME_CONFIG[theme] || null; +}; + +export const isKnownTheme = (theme) => { + return theme in THEME_CONFIG; +}; diff --git a/site/src/inspector_panel/utils/EntityProcessor.js b/site/src/inspector_panel/utils/EntityProcessor.js new file mode 100644 index 0000000..c41f535 --- /dev/null +++ b/site/src/inspector_panel/utils/EntityProcessor.js @@ -0,0 +1,16 @@ +/** + * Processes an activeFeature into an entity object for the inspector panel + * @param {Object} activeFeature - The active feature from the map + * @returns {Object} The processed entity object + */ +export const processActiveFeature = (activeFeature) => { + if (!activeFeature) { + return null; + } + + return { + theme: activeFeature.source, + type: activeFeature.sourceLayer, + ...activeFeature.properties, + }; +}; diff --git a/site/src/inspector_panel/utils/PanelTitleExtractor.js b/site/src/inspector_panel/utils/PanelTitleExtractor.js new file mode 100644 index 0000000..93a2e2d --- /dev/null +++ b/site/src/inspector_panel/utils/PanelTitleExtractor.js @@ -0,0 +1,24 @@ +/** + * Extracts the panel title from an entity, using names if available + * @param {Object} entity - The entity object + * @returns {string} The panel title + */ +export const extractPanelTitle = (entity) => { + // Default title + let panelTitle = "Inspector Panel"; + + // Try to extract title from names + if (entity["names"]) { + try { + const names = JSON.parse(entity["names"]); + if (names["primary"]) { + panelTitle = names["primary"]; + } + } catch (e) { + // If parsing fails, keep default title + console.warn("Failed to parse names for panel title:", e); + } + } + + return panelTitle; +}; diff --git a/site/src/inspector_panel/utils/PropertyOrderer.js b/site/src/inspector_panel/utils/PropertyOrderer.js new file mode 100644 index 0000000..0c90ced --- /dev/null +++ b/site/src/inspector_panel/utils/PropertyOrderer.js @@ -0,0 +1,36 @@ +/** + * Creates ordered keys for entity properties with custom class/subclass hierarchy + * @param {Object} entity - The entity object + * @returns {Array} Array of objects with key and indented properties + */ +export const createOrderedKeys = (entity) => { + // Get all keys except those starting with @ + const allKeys = Object.keys(entity).filter((key) => !key.startsWith("@")); + + // Create custom ordering for class/subclass hierarchy + const orderedKeys = []; + const processedKeys = new Set(); + + // First pass: add all keys except subclass + allKeys.forEach(key => { + if (key !== "subclass") { + orderedKeys.push({ key, indented: false }); + processedKeys.add(key); + + // If this is "class" and "subclass" exists, add subclass right after + if (key === "class" && entity.hasOwnProperty("subclass")) { + orderedKeys.push({ key: "subclass", indented: true }); + processedKeys.add("subclass"); + } + } + }); + + // Second pass: add any remaining keys that weren't processed + allKeys.forEach(key => { + if (!processedKeys.has(key)) { + orderedKeys.push({ key, indented: false }); + } + }); + + return orderedKeys; +};