{"id":15013036,"url":"https://github.com/mcnuttandrew/prong","last_synced_at":"2025-04-12T04:21:31.126Z","repository":{"id":182682130,"uuid":"433133459","full_name":"mcnuttandrew/prong","owner":"mcnuttandrew","description":"A projectional editor for JSON DSLs","archived":false,"fork":false,"pushed_at":"2024-03-30T08:48:29.000Z","size":9484,"stargazers_count":25,"open_issues_count":20,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-25T23:51:22.578Z","etag":null,"topics":["dsl","json","jsonschema","projectional-editor","structure-editor"],"latest_commit_sha":null,"homepage":"https://prong-editor.netlify.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mcnuttandrew.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-29T17:20:06.000Z","updated_at":"2025-02-16T18:07:17.000Z","dependencies_parsed_at":"2024-10-14T03:40:30.692Z","dependency_job_id":"8433f54a-5c44-4b7c-aeb8-bbfc130ede6d","html_url":"https://github.com/mcnuttandrew/prong","commit_stats":{"total_commits":141,"total_committers":2,"mean_commits":70.5,"dds":0.007092198581560294,"last_synced_commit":"f9731444079f6477d620a13a11b1a40b652dbcfe"},"previous_names":["mcnuttandrew/prong"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcnuttandrew%2Fprong","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcnuttandrew%2Fprong/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcnuttandrew%2Fprong/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcnuttandrew%2Fprong/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mcnuttandrew","download_url":"https://codeload.github.com/mcnuttandrew/prong/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248514427,"owners_count":21116963,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dsl","json","jsonschema","projectional-editor","structure-editor"],"created_at":"2024-09-24T19:43:38.368Z","updated_at":"2025-04-12T04:21:31.084Z","avatar_url":"https://github.com/mcnuttandrew.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Prong\n\nProng (PRojectional jsON Gui) is an editor framework for creating bespoke in-browser editors for JSON-based domain-specific languages (such as [Vega](https://vega.github.io/vega/), [Vega-Lite](https://vega.github.io/vega-lite/), [Tracery](https://tracery.io/), and [many others](https://vis-json-dsls.netlify.app/)). These editors allow for things like drag-and-drop interactions, inline-interactive spreadsheets, in-situ recommenders and sparklines, and many more elements that would require significant engineering effort to create otherwise.\n\nProng is a projectional editing system, which we see as being made up of two pieces:\n\n1. Structure editing, which allows you to manipulate text without requiring that you manually type out that text (we mostly do this through a special floating menu that is aware of the types of the DSLs). We achieve this by asking that you hand us a [JSON schema](https://json-schema.org/) describing your language.\n2. Alternative views (which we generally refer to as projections) that re-present parts of the text in means that are more meaningful to the domain at hand (like adding a dropdown for an field that has only a fixed set of options). We achieve this by asking you describe your projections using a little query language (see [below](#queries)) and plain ol react components.\n\nIn tandem this allows for some pretty interesting editing experiences to be made pretty easily, for instance:\n\n![Example image of the prong editor framework instantiated for a vega-lite style application](https://github.com/mcnuttandrew/prong/raw/main/example.png)\n\nSee the [docs site](https://prong-editor.netlify.app/) (where you may already be) to see a variety of examples. Please note that this is research grade software, so there are bugs and issues throughout, but we welcome any help or contributions you might wish to provide.\n\nThis work is described in much greater depth in our upcoming paper [\"Projectional Editors for JSON-based DSLs\"](http://arxiv.org/abs/2307.11260).\n\n## Quick start example usage\n\n```tsx\nimport { useState } from \"react\";\n\nimport { Editor, StandardBundle } from \"prong-editor\";\nimport \"prong-editor/style.css\";\n\nconst exampleData = `{\n    \"a\": {\n      \"b\": [1, 2, 3],\n      \"c\": true,\n    },\n    \"d\": null,\n    \"e\": [{ \"f\": 4, \"g\": 5 }],\n    \"I\": \"example\",\n  }`;\n\nfunction SimpleExample() {\n  const [currentCode, setCurrentCode] = useState(exampleData);\n\n  return (\n    \u003cEditor\n      schema={{}}\n      code={currentCode}\n      onChange={(x) =\u003e setCurrentCode(x)}\n      projections={Object.values(StandardBundle)}\n    /\u003e\n  );\n}\n```\n\nTo install follow the usual methods:\n\n```\nyarn add prong-editor\n```\n\nDont forget to import our css file!\n\n### What about authoring my own projections??\n\nAuthoring your own projections is also reasonably straightforward. We have a number of examples throughout the code base that you might look at for inspiration of how to define ad hoc projections (cf [sites/docs/src/examples/](https://github.com/mcnuttandrew/prong/tree/main/sites/docs/src/examples)) as well as more structured repeatable ones (cf [packages/prong-editor/projections/](https://github.com/mcnuttandrew/prong/tree/main/packages/prong-editor/src/projections)). But should you want a quick start, here's an example projection that will appear in the floating tooltip menu\n\n```tsx\nimport { utils, Projection } from \"prong-editor\";\nimport friendlyWords from \"friendly-words\";\n\nconst titleCase = (word: string) =\u003e `${word[0].toUpperCase()}${word.slice(1)}`;\nconst pick = (arr: any[]) =\u003e arr[Math.floor(Math.random() * arr.length)];\n\nfunction generateName() {\n  const adj = pick(friendlyWords.predicates);\n  const obj = titleCase(pick(friendlyWords.objects));\n  return `${adj}${obj}`;\n}\n\nconst RandomWordProjection: Projection = {\n  // where to put the projection\n  query: { type: \"regex\", query: /\".*\"/ },\n  // should it appear in the tooltip or inline?\n  type: \"tooltip\",\n  // what should it look like\n  projection: ({ keyPath, setCode, fullCode }) =\u003e {\n    const click = () =\u003e\n      setCode(utils.setIn(keyPath, `\"${generateName()}\"`, fullCode));\n    return \u003cbutton onClick={click}\u003eRandom Word\u003c/button\u003e;\n  },\n  // what group should the projection appear in\n  group: \"Utils\",\n  name: \"Random Word\"\n};\n\nexport default RandomWordProjection;\n```\n\nSee below for [additional details](#projections) on the semantics of projection definition.\n\n### Gotchas\n\n- We don't automatically import schemas. Its very easy to import a schema (they are just JSON after all) and so we would prefer not to create an import dep for you\n\n- The editor excepts a string! It is very easy to accidentally forget and hand it a parsed object rather than a string describing a json object.\n\n- This system isn't really for interacting with data. There are lots of other great systems for wrangling JSON data of various kinds (such as [JSON Crack](https://jsoncrack.com/), [jq](https://jqlang.github.io/jq/), and many others), it's just for DSL style usage. The affordances required for each type of usage are related, but are somewhat distinct!\n\n## Component\n\nThe library consists of a single component it has a type like\n\n```tsx\n\u003cEditor {...{\n  onChange: (code: string) =\u003e void;\n  code: string;\n  schema: JSONSchema;\n  projections?: Projection[];\n  height?: string;\n  onTargetNodeChanged?: (newNode: any, oldNode: any) =\u003e void;\n}} /\u003e\n```\n\n## Standard Bundle\n\nWe include a variety of common projections that you might find useful\n\n```tsx\n{\n  BooleanTarget, // add check boxes to boolean\n    CleanUp, // add a \"clean up\" button to the menu that pretty formats the code\n    ClickTarget, // add like rectangles that make it feel nice to click {s\n    ColorChip, // add a little colored circle next to colors\n    ConvertHex, // add a button to the tooltip that allows you to convert named colors to hex\n    Debugger, // for each AST show all the information we have about it in the tooltip\n    NumberSlider, // number slider\n    SortObject, // Sort the keys in an object\n    TooltipColorNamePicker, // select a named color from a fancy menu of web colors\n    TooltipHexColorPicker; // select a color using a hex color picker\n}\n```\n\nYou dont have to include any of them or all of them, its presented as an object so you can select what you want.\n\n## Utils\n\nWe provide a handful utilities to make the construction of these editors less painful:\n\n```ts\n// make a simple modification to a json string\nfunction setIn(\n  keyPath: (string | number)[],\n  newValue: any,\n  content: string\n): string;\n```\n\n```ts\n// a simple prettification algorithm tuned to json specifically\nfunction prettifier(\n  passedObj: any,\n  options?: {\n    indent?: string | undefined;\n    maxLength?: number | undefined;\n    replacer?: ((this: any, key: string, value: any) =\u003e any) | undefined;\n  }\n): string;\n```\n\n```ts\n// a simple wrapper around a forgiving json parser\nfunction simpleParse(content: any, defaultVal?: {}): any;\n```\n\n```ts\n// maybe remove double quotes from a string, handy for some styling tasks\nconst maybeTrim: (x: string) =\u003e string;\n```\n\n## Projections\n\nThe central design abstraction in Prong are projections. These are lightweight ways to modify the text within the editor to fit your goals.\n\nThere are four types of projections.\n\n### Tooltip Projection Projection\n\nThis projection creates a menu item that will appear in the tooltip, monocle, or dock (depending on what the user wants in a given moment).\n\n```tsx\n{\n  // the name of the projection, used to opt in/opt out\n  name: string;\n  // the heading that the projection will appear under\n  group: string;\n  projection: (props: ProjectionProps) =\u003e JSX.Element;\n  query: ProjectionQuery;\n  type: \"tooltip\";\n  // whether or not this projection takes over the whole menu\n  // note that the first provided projections takes precedence\n  takeOverMenu?: boolean;\n}\n```\n\nSee [Queries](#queries) below for an example of the query system.\nThe name describe which heading the projection will be grouped into.\nThe projection creates the specific element that inserted into the menu, it expects a function that returns a react component. It gets props like\n\n```tsx\ninterface ProjectionProps {\n  //  code snippet of the current node, it is provided as a convenience as you could get it from fullCode.slice(node.from, node.to)\n  currentValue: any;\n\n  // a list of cursor positions, it is useful for interacting with the cursor. diagnosticErrors is an array of lint errors.\n  cursorPositions: any[];\n\n  // lint errors from the current position (based on the schema)\n  diagnosticErrors: Diagnostic[];\n\n  // the full code in the document at the current moment.\n  fullCode: string;\n\n  // the access path for the value in the json object, note that if trying to access the value in a property (eg if you have [{\"a\": \"b\"}] and you want b) you need to add a `___value` tailing element. So for that example we would do `[0, \"a\", \"a___value\"]`.\n  keyPath: (string | number)[];\n\n  // the AST node generated by code mirror (see [their docs](https://lezer.codemirror.net/docs/ref/#common.SyntaxNode) for more details).\n  node: SyntaxNode;\n\n  // allows you to set the code in the document, it will trigger an onUpdate event.\n  setCode: (code: string) =\u003e void;\n\n  // typings the inferred typings from the JSON Schema for the node.\n  typings: any[];\n}\n```\n\n### Inline Projection\n\nUse this projection type to place projections into the editor itself.\n\n```tsx\n{\n  // the name of the projection, used to opt in/opt out\n  name: string;\n  // whether or not the react component has internal state, more aggressively removed it does not have internal state\n  hasInternalState: boolean;\n  // if a projection requires multiple lines of a DSL (say bc you are replacing a multiline data set or something) you must use replace-multiline\n  mode: \"replace\" | \"prefix\" | \"suffix\" | \"replace-multiline\";\n  projection: (props: ProjectionProps) =\u003e JSX.Element;\n  query: ProjectionQuery;\n  type: \"inline\";\n}\n```\n\n### Highlight Projection\n\nThis simplest of the projections allows you to add a css class to whatever elements in the editor you might wish to. See examples/TraceryExample for example usage.\n\n```tsx\n{\n  // the name of the projection, used to opt in/opt out\n  name: string;\n  class: string;\n  query: ProjectionQuery;\n  type: \"highlight\";\n}\n```\n\n## Queries\n\nA critical part of the projection system are the queries. These small functions allow the system to check when and where each component should be inserted.\n\nThese come in a variety of flavors\n\n- **Index Queries**:\n\n```tsx\n{ type: \"index\"; query: (number | string)[] }\n```\n\nWhere number|string is a key path. Note that is strictly the fastest and most accurate of the query types, as we can identify things unambiguously.\n\n- **Regex Queries**:\n\n```tsx\n{\n  type: \"regex\";\n  query: RegExp;\n}\n```\n\nCheck if a value matches a regex\n\n- **Value Queries**:\n\n```tsx\n{ type: \"value\"; query: string[] }\n```\n\nCheck if a value is equal to any of several strings.\n\n- **Schema Queries**:\n\n```tsx\n{ type: \"schemaMatch\"; query: string[] }\n```\n\nCheck if a node has inferred type (from the JSON Schema) equal to one of several node types. Refer to your json schema for the names that are checked.\n\n- **Node Type Queries**:\n\n```tsx\n{ type: \"nodeType\"; query: NodeType[] };\n```\n\nThese queries allow you to check for a given AST node type. The JSON AST includes the following symbols: `String`, `Number`, `True`, `False`, `Null`, `Object`, `Array`, `Property`, `PropertyName`, `{`, `},` `[`, `]`, and `⚠` (which describes parse errors).\n\n- **Function Queries**:\n\n```tsx\n{\n  type: \"function\";\n  query: (\n    value: string,\n    nodeType: NodeType,\n    keyPath: KeyPath,\n    cursorPos: number,\n    nodePos: { start: number; end: number }\n  ) =\u003e boolean;\n}\n```\n\nIf none of these work for you there is also a function query type. This is obviously the most expensive to run, so should be avoided where possible. Useful for doing checks within a string.\n\n## Local development\n\nClone the repo as you might usually.\n\n1. install package deps (cd packages/prong-editor, yarn)\n2. install docs deps (cd sites/docs, yarn)\n3. run some scripts (cd sites/docs, yarn post-build, yarn prep-data)\n4. Run locally (cd sites/docs, yarn dev)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmcnuttandrew%2Fprong","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmcnuttandrew%2Fprong","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmcnuttandrew%2Fprong/lists"}