Add a new page to the dotabase Vue app: an OpenDota Match Explorer that lets users interactively build and execute queries against the OpenDota /players/{account_id}/matches endpoint, preview the returned JSON, and copy the generated URL.
This is a self-contained page — either a new HTML entry point (like vpk_browser.html) or a new route added if routing is introduced. The simplest approach is a new standalone HTML file at src/opendota.html with its own Vue app in src/opendota_app.vue.
Follow the same patterns as the existing src/index.html + src/App.vue:
- Load dillerm.css from
https://tools.dillerm.io/lib/dillerm.css?version=dev - Load dillerm.umd.js and call
DillermWebUtils.init()for the navbar - Mount a Vue 3 app into
#vueapp - Use
DillermSelect,DillermTextfrom@dillerm/webutils
A prominent bar at the top showing the currently constructed API URL. Should:
- Be a large, readable, copyable element (full-width)
- Update live as params change
- Have a "Copy" button that copies the URL to clipboard and briefly shows "Copied!"
- Show the full URL including
https://api.opendota.com/api/players/{account_id}/matches?... - Use
background-color: var(--background-color3)and a monospace font
A grid or two-column layout of filter controls. Each control corresponds to a param from src/assets/opendota_matchfilter.json. Use that JSON file as the source of truth for all available params, their types, options, and descriptions.
Control types to support:
text→DillermTextcomponentnumber→DillermTextwithtype="number"or<input type="number">select→DillermSelectwith options from the JSONmultiselect→ multiple checkboxes or a multi-select component forprojectfields (each selected value becomes a separateproject=valuequery param)
Each control should show:
- A label
- The control itself
- A small description/hint below (from the JSON
descriptionfield) - A
notesbadge if the field has anotesproperty (e.g. "Requires parsed matches")
Param controls layout:
account_idalways at the top, full-width, prominent- Group remaining params visually: Core (limit, offset, win, lobby_type), Game (game_mode, significant), Match (hero_id, with_hero_id, against_hero_id, party_size, lane_role), Players (included_account_id, excluded_account_id), Time/Place (date, region), Advanced (project fields)
A big "Fetch Matches" button. Clicking it:
- Makes a real
fetch()call tohttps://api.opendota.com/api/players/{account_id}/matches?{params} - Shows a loading state
- On success: displays the result count and renders the JSON response
After fetching, show:
- A summary bar: "X matches returned in Yms"
- A table using the existing
ResultTable.vuecomponent pattern (or a simplified inline version if easier) showing the match data - The raw JSON in a
<pre>tag inside a collapsible<details>block below the table
Build the query string from active params:
- Skip params with null/empty values
- For
project(multiselect): emit multipleproject=valueparams, one per selected field - For path param
account_id: substitute into the URL path, not the query string - Encode values properly with
encodeURIComponent
Example output:
https://api.opendota.com/api/players/87287966/matches?win=1&lobby_type=7&hero_id=75&date=30&limit=20&project=kills&project=deaths&project=assists
Load the examples array from opendota_matchfilter.json. Show them as a row of clickable preset buttons (or a dropdown). Clicking a preset:
- Populates all param controls from the preset's
paramsobject - Triggers a live URL update (and optionally auto-fetches)
data() {
return {
account_id: "", // string, typed by user
params: {}, // { key: value } for all active query params
project_fields: [], // array of selected project field strings
result: null, // raw response array
status: "idle", // "idle" | "loading" | "success" | "error"
status_text: "",
copied: false, // for copy button feedback
}
}No backend changes needed — this page calls the OpenDota API directly from the browser. The Express server (server.js) just needs to serve the new HTML file as a static file from the build/ directory (which Vite handles automatically).
Add the new entry point to vite.config.js:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
root: "src",
build: {
rollupOptions: {
input: {
main: "src/index.html",
opendota: "src/opendota.html" // add this
},
outDir: "../build"
}
},
server: {
proxy: {
"/api": "http://localhost:3000",
"/vpk": "http://localhost:3000"
}
}
});- Use the same dark theme CSS variables as the rest of the project (
--background-color1through--background-color4,--highlight-color1,--text-color,--input-highlight-color) - URL bar:
background-color: var(--background-color4),border: 1px solid var(--highlight-color1),padding: 12px 16px,border-radius: 5px,font-family: monospace - Param groups:
background-color: var(--background-color2),border-radius: 5px,padding: 15px,margin-bottom: 10px - "Fetch" button:
background-color: var(--highlight-color1),color: white, large and prominent - Result table: reuse
ResultTable.vueor style similar to it - Notes badges: small inline chip,
background-color: var(--background-color4), amber/yellow text
| File | Purpose |
|---|---|
src/opendota.html |
New page entry point (mirrors index.html pattern) |
src/OpenDotaApp.vue |
Main Vue component for the page |
| File | Change |
|---|---|
vite.config.js |
Add opendota.html as a build entry point |
| File | Why it's useful |
|---|---|
src/assets/opendota_matchfilter.json |
Source of truth for all params and examples |
src/index.html |
Copy navbar/app setup pattern |
src/App.vue |
Component patterns, DillermSelect/DillermText usage |
src/components/ResultTable.vue |
Reuse for displaying match rows |
src/components/StatusBar.vue |
Reuse for fetch status |
- Link each match_id in the results to
https://www.opendota.com/matches/{match_id} - Auto-fetch when account_id is typed (debounced, 800ms)
- Show a warning if
lane_roleis selected butlaneandis_roamingare not in project fields - Link to the OpenDota docs URL from the JSON (
docs_url) in the header - Show the OpenDota rate limit remaining (returned in response headers as
x-rate-limit-remaining-month)