diff --git a/MODULE.bazel b/MODULE.bazel index cad256cd..7b685eb4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,7 +1,7 @@ module( name = "claro-lang", repo_name = "claro-lang", - version = "0.1.409", + version = "0.1.499", ) bazel_dep(name = "aspect_bazel_lib", version = "2.0.1") diff --git a/logo/BUILD b/logo/BUILD index 2e810d9b..34459822 100644 --- a/logo/BUILD +++ b/logo/BUILD @@ -1,3 +1,5 @@ +load("@aspect_rules_js//js:defs.bzl", "js_library") + exports_files([ "ClaroLogoFromArrivalHeptapodOfferWeapon1.jpeg", "ClaroLogoFromArrivalHeptapodOfferWeapon-transparentBackground.png", diff --git a/tools/clarodocs/index.html b/tools/clarodocs/index.html index 85fea98c..adf48eef 100644 --- a/tools/clarodocs/index.html +++ b/tools/clarodocs/index.html @@ -2,9 +2,9 @@ - + - + - React App + ClaroDocs diff --git a/tools/clarodocs/package.json b/tools/clarodocs/package.json index 9bb803ac..1cd58d69 100644 --- a/tools/clarodocs/package.json +++ b/tools/clarodocs/package.json @@ -10,6 +10,7 @@ "mermaid": "10.8.0", "react": "~18.2.0", "react-dom": "~18.2.0", + "react-router-dom": "^6.22.3", "web-vitals": "2.1.4" }, "devDependencies": { diff --git a/tools/clarodocs/pnpm-lock.yaml b/tools/clarodocs/pnpm-lock.yaml index 30f5e9f6..ffe062ac 100644 --- a/tools/clarodocs/pnpm-lock.yaml +++ b/tools/clarodocs/pnpm-lock.yaml @@ -25,6 +25,9 @@ dependencies: react-dom: specifier: ~18.2.0 version: 18.2.0(react@18.2.0) + react-router-dom: + specifier: ^6.22.3 + version: 6.22.3(react-dom@18.2.0)(react@18.2.0) web-vitals: specifier: 2.1.4 version: 2.1.4 @@ -2120,6 +2123,11 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@remix-run/router@1.15.3: + resolution: {integrity: sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==} + engines: {node: '>=14.0.0'} + dev: false + /@rollup/pluginutils@5.1.0: resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} @@ -6801,6 +6809,29 @@ packages: engines: {node: '>=0.10.0'} dev: true + /react-router-dom@6.22.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + '@remix-run/router': 1.15.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-router: 6.22.3(react@18.2.0) + dev: false + + /react-router@6.22.3(react@18.2.0): + resolution: {integrity: sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + dependencies: + '@remix-run/router': 1.15.3 + react: 18.2.0 + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} diff --git a/tools/clarodocs/public/claro-logo-192x192.png b/tools/clarodocs/public/claro-logo-192x192.png new file mode 100644 index 00000000..1f956ad9 Binary files /dev/null and b/tools/clarodocs/public/claro-logo-192x192.png differ diff --git a/tools/clarodocs/public/claro-logo-512x512.png b/tools/clarodocs/public/claro-logo-512x512.png new file mode 100644 index 00000000..7729acc6 Binary files /dev/null and b/tools/clarodocs/public/claro-logo-512x512.png differ diff --git a/tools/clarodocs/public/claro-logo.ico b/tools/clarodocs/public/claro-logo.ico new file mode 100644 index 00000000..fd54862e Binary files /dev/null and b/tools/clarodocs/public/claro-logo.ico differ diff --git a/tools/clarodocs/public/favicon.ico b/tools/clarodocs/public/favicon.ico deleted file mode 100644 index a11777cc..00000000 Binary files a/tools/clarodocs/public/favicon.ico and /dev/null differ diff --git a/tools/clarodocs/public/logo192.png b/tools/clarodocs/public/logo192.png deleted file mode 100644 index fc44b0a3..00000000 Binary files a/tools/clarodocs/public/logo192.png and /dev/null differ diff --git a/tools/clarodocs/public/logo512.png b/tools/clarodocs/public/logo512.png deleted file mode 100644 index a4e47a65..00000000 Binary files a/tools/clarodocs/public/logo512.png and /dev/null differ diff --git a/tools/clarodocs/public/manifest.json b/tools/clarodocs/public/manifest.json index 080d6c77..fe1c1a99 100644 --- a/tools/clarodocs/public/manifest.json +++ b/tools/clarodocs/public/manifest.json @@ -1,25 +1,25 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "ClaroDocs", + "name": "ClaroDocs", "icons": [ { - "src": "favicon.ico", + "src": "claro-logo.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { - "src": "logo192.png", + "src": "claro-logo-192x192.png", "type": "image/png", "sizes": "192x192" }, { - "src": "logo512.png", + "src": "claro-logo-512x512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", - "theme_color": "#000000", + "theme_color":"#ffffff", "background_color": "#ffffff" } diff --git a/tools/clarodocs/src/App.tsx b/tools/clarodocs/src/App.tsx index 65336909..020919d2 100644 --- a/tools/clarodocs/src/App.tsx +++ b/tools/clarodocs/src/App.tsx @@ -7,24 +7,53 @@ import { HighlightJS } from './components/highlight_js/HighlightJS'; import { DepGraph } from './components/dep_graph/DepGraph'; import { WholeProgramDepGraph } from './components/dep_graph/WholeProgramDepGraph'; import { ModuleTree } from './components/module_tree/ModuleTree'; +import { + createBrowserRouter, + RouterProvider, + useSearchParams +} from "react-router-dom"; +// import { useSearchParamsState } from './hooks/UseSearchParamsHook'; import { Breadcrumb, ConfigProvider, Layout, Menu, theme } from 'antd'; const { Header, Sider, Content } = Layout; -function App() { - const [showDepGraph, setShowDepGraph] = useState(true); - const { - token: { colorBgContainer, borderRadiusLG }, - } = theme.useToken(); - - const [ selectedModule, setSelectedModule ] = useState(''); - const [ targetType, setTargetType ] = useState(''); +function AppImpl() { + const [ searchParams, setSearchParams ] = useSearchParams(); - const [ ClaroModules, rootName, rootDeps ] = getClaroModules(setSelectedModule); + const showDepGraph = searchParams.get('showDepGraph') === null || searchParams.get('showDepGraph') === 'true'; + let selectedModule = searchParams.get('selectedModule') || ''; + const targetType = searchParams.get('targetType') || ''; - // We'll reuse these tooltip components every time the dep is referenced in the Module API. - const selectedModuleDepsTooltips = ClaroModules[selectedModule]?.deps; + const setShowDepGraph = function() { + setSearchParams({ + showDepGraph: !showDepGraph, + selectedModule: '', + targetType: '' + }); + } + const setSelectedModule = function(newSelectedModule) { + setSearchParams({ + showDepGraph: false, + selectedModule: newSelectedModule, + targetType: targetType + }); + } + const setSelectedModuleAndTargetType = function(newSelectedModule, newTargetType) { + setSearchParams({ + showDepGraph: false, + selectedModule: newSelectedModule, + targetType: newTargetType + }); + } + const setTargetType = function(newTargetType) { + setSearchParams({ + showDepGraph: showDepGraph, + selectedModule: selectedModule, + targetType: newTargetType + }); + } + const [ ClaroModules, rootName, rootDeps ] = getClaroModules(setSelectedModule); useEffect( () => { @@ -42,6 +71,19 @@ function App() { ); function APIs() { + if (!selectedModule) { + // Ensure that this still works even if the user just clicked the APIs tab directly without manually selecting a + // module. + selectedModule = Object.keys(ClaroModules).toSorted()[0]; + } + + // We'll reuse these tooltip components every time the dep is referenced in the Module API. + const selectedModuleDepsTooltips = ClaroModules[selectedModule]?.deps; + + const { + token: { colorBgContainer, borderRadiusLG }, + } = theme.useToken(); + return ( @@ -131,9 +173,8 @@ function App() { className="claro" style={{ textAlign: "left" }} id={selectedModule} - setSelectedModule={setSelectedModule} targetType={targetType} - setTargetType={setTargetType} + setSelectedMOduleAndTargetType={setSelectedModuleAndTargetType} selectedModuleDepsTooltips={selectedModuleDepsTooltips} > {ClaroModules[selectedModule]?.api} @@ -152,7 +193,6 @@ function App() { modules={ClaroModules} selectedModule={selectedModule} setSelectedModule={setSelectedModule} - setShowDepGraph={setShowDepGraph} /> ); } @@ -170,14 +210,14 @@ function App() { return (
-
+
setShowDepGraph(!showDepGraph)} + onClick={({ key }) => setShowDepGraph()} style={{ flex: 1, minWidth: 200 }} />
@@ -189,4 +229,17 @@ function App() { ); } +function App() { + // We'll wrap everything in a React Router literally just so that it can easily maintain state from query params. + // The useSearchParams() function simply doesn't work outside of this RouterProvider context. Lame. + const router = createBrowserRouter([ + { + path: "/", + element: , + }, + ]); + + return +} + export default App; \ No newline at end of file diff --git a/tools/clarodocs/src/BUILD b/tools/clarodocs/src/BUILD index a2855d4a..5fe0171c 100644 --- a/tools/clarodocs/src/BUILD +++ b/tools/clarodocs/src/BUILD @@ -33,6 +33,7 @@ ts_project( "//tools/clarodocs/src/components/input", "//tools/clarodocs/src/components/module_tree", "//tools/clarodocs/src/claro_module_apis", + "//tools/clarodocs/src/hooks:use_search_params_hook", ], ) diff --git a/tools/clarodocs/src/claro_module_apis/demo.tsx b/tools/clarodocs/src/claro_module_apis/demo.tsx index 76e0221c..98f89c10 100644 --- a/tools/clarodocs/src/claro_module_apis/demo.tsx +++ b/tools/clarodocs/src/claro_module_apis/demo.tsx @@ -17,7 +17,7 @@ export function getClaroModules(setSelectedModule) { // Copy the data so that it's not overwritten. const fmtMod = formatUniqueModuleName(mod); claroModules[fmtMod] = {}; - claroModules[fmtMod]['api'] = data.depGraph[mod].api; + claroModules[fmtMod]['api'] = data.depGraph[mod].api.trim(); claroModules[fmtMod]['deps'] = {}; for (let dep of Object.keys(data.depGraph[mod].deps)) { diff --git a/tools/clarodocs/src/claro_module_apis/testjson.json b/tools/clarodocs/src/claro_module_apis/testjson.json deleted file mode 100644 index 8d45af30..00000000 --- a/tools/clarodocs/src/claro_module_apis/testjson.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "stdlib$http$http": { - "api": "\nfunction getOk200HttpResponseForHtml(json: string) -> HttpResponse;\n\nfunction getOk200HttpResponseForJson(json: string) -> HttpResponse;\n", - "deps": {} - }, - "examples$claro_programs$demo_server$buggy_buggies$buggy_buggies_service$buggy_buggies_client": { - "api": "\n# This encodes the public API of the Buggy-Buggies site available at https://buggy-buggies.gigalixirapp.com/.\n# Claro will generate a non-blocking RPC client for you via the following:\n# `var myClient: HttpClient = getHttpClient(\"https://buggy-buggies.gigalixirapp.com\");`\nHttpService BuggyBuggies {\n hostGame: \"/api/host/{handle}\",\n friendsJoin: \"/api/game/{gameId}/join/{handle}\",\n move: \"/api/game/{gameId}/player/{secret}/move/{direction}\",\n worldInfo: \"/api/game/{gameId}/player/{secret}/info\",\n reset: \"/api/game/{gameId}/player/{secret}/reset\"\n}\n\n# Now there's a single static definition of which client will be used for sending reqs to the Buggy Buggies server.\nstatic HTTP_CLIENT: HttpClient;\n\n# This type models the JSON response from the Buggy-Buggies service as a Claro type to enable parsing the response into\n# something that you can work with programmatically with strict type validation on the edge.\nalias MoveResponse : struct {\n reason: oneof,\n result: struct {\n players: { string : struct { x: int, y: int } },\n dimensions: struct {\n height: int,\n width: int\n },\n world: { string: string }, # E.g. (\"0,6\": \"wall\")\n you: struct {\n handle: string,\n purse: int,\n boom: boolean,\n x: int,\n y: int\n }\n },\n success: boolean\n}\n\nfunction getParsedMoveResponse(buggyResponse: string) -> std::ParsedJson;\n", - "deps": {} - }, - "examples$claro_programs$demo_server$buggy_buggies$data_structures$default_dict": { - "api": "newtype DefaultDict: struct {defaultPr: provider, dict: mut {K:V}}\n\ninitializers DefaultDict {\n function create(defaultPr: provider) -> DefaultDict;\n}\n\n# TODO(steving) Claro really needs to support overloading operator[] via some Contract.\nunwrappers DefaultDict {\n function get(d: DefaultDict, key: K) -> V;\n consumer put(d: DefaultDict, key: K, value: V);\n function asMap(d: DefaultDict) -> {K: V};\n}\n", - "deps": {} - }, - "examples$claro_programs$demo_server$buggy_buggies$data_structures$position": { - "api": "\nnewtype Position : struct {x: int, y: int}\n", - "deps": {} - }, - "examples$claro_programs$demo_server$buggy_buggies$data_structures$heap": { - "api": "\n# This implementation is the result of asking Chat GPT to implement it in python for me and reworking it to fit in Claro\n# I'm not thinking too hard about this...\nnewtype Heap: mut [struct {dist: int, pos: Pos::Position}]\n\ninitializers Heap {\n provider getHeap() -> Heap;\n}\n\nunwrappers Heap {\n function extract_min(heap: Heap) -> oneof>;\n consumer insert(heap: Heap, value: struct {dist: int, pos: Pos::Position});\n function heapIsEmpty(heap: Heap) -> boolean;\n}\n", - "deps": {"Pos": "examples$claro_programs$demo_server$buggy_buggies$data_structures$position"} - }, - "examples$claro_programs$demo_server$buggy_buggies$buggy_agent$buggy_agent": { - "api": "\nalias BestPath : [Pos::Position]\n\nfunction parseWorldMap(world: {string: string}) -> {Pos::Position: string};\nfunction dijkstra(world: {Pos::Position: string}, start: Pos::Position) -> BestPath;\nfunction movesFromBestPath(bestPath: BestPath) -> [string];\n", - "deps": {"DefaultDict": "examples$claro_programs$demo_server$buggy_buggies$data_structures$default_dict", - "Heaps": "examples$claro_programs$demo_server$buggy_buggies$data_structures$heap", - "Pos": "examples$claro_programs$demo_server$buggy_buggies$data_structures$position"} - }, - "examples$claro_programs$demo_server$buggy_buggies$utils$utils": { - "api": "# This module is simply a dumping ground of uncategorized utility functions.\n\nfunction handleBuggyResponseAsHtmlStrParts(buggyResponse: oneof>) -> [string];\nfunction reduce(l: [T], accumulated: R, accumulatorFn: function<|R, T| -> R>) -> R;\n", - "deps": {} - }, - "examples$claro_programs$demo_server$buggy_buggies$endpoint_handlers$resources$resources": { - "api": "\nstatic gamePageHtml : string;\n", - "deps": {} - }, - "examples$claro_programs$demo_server$buggy_buggies$endpoint_handlers$endpoint_handlers": { - "api": "\n# It's worth noting that in the Claro way of the world, this distinction of whether a Procedure is implemented as a\n# Graph or not is considered completely an internal implementation consideration that's not directly observable with\n# in-language semantics. Hence, this is *actually* implemented internally as a Graph Procedure, but that detail just\n# isn't exposed here.\nprovider gamePageHandler() -> future;\n\nfunction startNewGameHandler(handle: string) -> future;\n\nfunction getBestMovesHandler(buggyResponse: oneof>) -> string;\n\nfunction gameMoveHandler(gameId: string, playerSecret: string, dir: string) -> future;\n", - "deps": {"Agent": "examples$claro_programs$demo_server$buggy_buggies$buggy_agent$buggy_agent", - "BuggyBuggies": "examples$claro_programs$demo_server$buggy_buggies$buggy_buggies_service$buggy_buggies_client", - "Utils": "examples$claro_programs$demo_server$buggy_buggies$utils$utils", - "Pos": "examples$claro_programs$demo_server$buggy_buggies$data_structures$position", - "Resources": "examples$claro_programs$demo_server$buggy_buggies$endpoint_handlers$resources$resources"} - } -} diff --git a/tools/clarodocs/src/components/dep_graph/WholeProgramDepGraph.tsx b/tools/clarodocs/src/components/dep_graph/WholeProgramDepGraph.tsx index 2c99f538..c755635d 100644 --- a/tools/clarodocs/src/components/dep_graph/WholeProgramDepGraph.tsx +++ b/tools/clarodocs/src/components/dep_graph/WholeProgramDepGraph.tsx @@ -1,6 +1,6 @@ import { Mermaid } from '../mermaid/Mermaid'; -export function WholeProgramDepGraph({ rootName, rootDeps, modules, selectedModule, setSelectedModule, setShowDepGraph }) { +export function WholeProgramDepGraph({ rootName, rootDeps, modules, selectedModule, setSelectedModule}) { let mermaidGraph = 'graph LR;\n'; for (let rootDep of Object.keys(rootDeps).toSorted()) { @@ -24,8 +24,7 @@ export function WholeProgramDepGraph({ rootName, rootDeps, modules, selectedModu } // Every node navigates to its Module API page. nodeOnClickCallbacks[module[0]] = () => { - setSelectedModule(module[0]); - setShowDepGraph(false); // Navigate to the API. + setSelectedModule(module[0]); // Navigate to the API. } } diff --git a/tools/clarodocs/src/components/highlight_js/HighlightJS.tsx b/tools/clarodocs/src/components/highlight_js/HighlightJS.tsx index 95b3f0d9..68f050ed 100644 --- a/tools/clarodocs/src/components/highlight_js/HighlightJS.tsx +++ b/tools/clarodocs/src/components/highlight_js/HighlightJS.tsx @@ -47,8 +47,7 @@ export class HighlightJS extends React.Component { const nextNode = codeEl.childNodes[i + 1]; if (this.isExpectedSpan(nextNode, '::')) { // Make sure that `this` doesn't escape the current scope. - const setSelectedModule = this.props.setSelectedModule; - const setTargetType = this.props.setTargetType; + const setSelectedMOduleAndTargetType = this.props.setSelectedMOduleAndTargetType; const depName = el.textContent.trim(); const targetType = codeEl.childNodes[i + 2].textContent.trim(); @@ -62,8 +61,7 @@ export class HighlightJS extends React.Component { const _selectedModule = this.props.selectedModuleDepsTooltips[depName].path; function _setSelectedModule(e) { - setSelectedModule(_selectedModule); - setTargetType(targetType); + setSelectedMOduleAndTargetType(_selectedModule, targetType); } // First replace the dep reference with its tooltip const newTargetTypeNode = document.createElement('span'); diff --git a/tools/clarodocs/src/hooks/BUILD b/tools/clarodocs/src/hooks/BUILD new file mode 100644 index 00000000..d11553de --- /dev/null +++ b/tools/clarodocs/src/hooks/BUILD @@ -0,0 +1,15 @@ +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") +load("//tools/clarodocs:defs.bzl", "TRANSPILER") + +ts_project( + name = "use_search_params_hook", + srcs = ["UseSearchParamsHook.tsx"], + declaration = True, + resolve_json_module = True, + transpiler = TRANSPILER, + tsconfig = "//tools/clarodocs:tsconfig", + visibility = ["//tools/clarodocs:__subpackages__"], + deps = [ + "//tools/clarodocs:node_modules/react-router-dom", + ], +) diff --git a/tools/clarodocs/src/hooks/UseSearchParamsHook.tsx b/tools/clarodocs/src/hooks/UseSearchParamsHook.tsx new file mode 100644 index 00000000..3147f572 --- /dev/null +++ b/tools/clarodocs/src/hooks/UseSearchParamsHook.tsx @@ -0,0 +1,28 @@ +// Documented at https://blog.logrocket.com/use-state-url-persist-state-usesearchparams/. +import { useSearchParams } from "react-router-dom"; + +export function useSearchParamsState( + searchParamName: string, + defaultValue: string +): readonly [ + searchParamsState: string, + setSearchParamsState: (newState: string) => void +] { + const [searchParams, setSearchParams] = useSearchParams(); + + const acquiredSearchParam = searchParams.get(searchParamName); + const searchParamsState = acquiredSearchParam ?? defaultValue; + + const setSearchParamsState = (newState: string) => { + const next = Object.assign( + {}, + [...searchParams.entries()].reduce( + (o, [key, value]) => ({ ...o, [key]: value }), + {} + ), + { [searchParamName]: newState } + ); + setSearchParams(next); + }; + return [searchParamsState, setSearchParamsState]; +} \ No newline at end of file