Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: LEAP-1341: Build schema.json in create-docs script #6263

Merged
merged 17 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions docs/source/tags/timelinelabels.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
title: TimelineLabels
type: tags
order: 429
is_new: t
meta_title: TimelineLabels tag
meta_description: Classify video frames using TimelineLabels.
---
Expand All @@ -11,8 +10,6 @@ Use the TimelineLabels tag to classify video frames. This can be a single frame

First, select a label and then click once to annotate a single frame. Click and drag to annotate multiple frames.

To move forward and backward in the timeline without labeling, ensure that no labels are selected before you click.

![Screenshot of video with frame classification](../images/timelinelabels.png)

Use with the `<Video>` control tag.
Expand Down
185 changes: 138 additions & 47 deletions web/apps/labelstudio/src/pages/CreateProject/Config/schema.json

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions web/libs/editor/scripts/create-docs.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/**
* This file is used to parse JSDoc for every tag and their regions
* and generate two artifacts out of it:
* - tag docs for https://labelstud.io/tags/
* generated docs are written to `outputDirArg` (1st arg)
* - schema.json — a dictionary for auto-complete in config editor
* generated file is written to `schemaJsonPath` (2nd arg or `SCHEMA_JSON_PATH` env var)
*
* Special new constructions:
* - `@regions` to reference a Region tag(s) used by current tag
* - `@subtag` to mark a tag used inside other tag (only Channel for now)
*
* Usage:
* node scripts/create-docs.js [path/to/docs/dir] [path/to/schema.json]
*/

const jsdoc2md = require("jsdoc-to-markdown");
const fs = require("fs");
const path = require("path");
Expand All @@ -8,13 +24,26 @@ const groups = [
{ dir: "visual", title: "Visual & Experience", order: 501 },
];

// tags that have subtags
const supertags = ["TimeSeries"];

// glob pattern to check all possible extensions
const EXT = "{js,jsx,ts,tsx}";

const currentTagsUrl = "https://api.github.com/repos/heartexlabs/label-studio/contents/docs/source/tags";

/**
* Convert jsdoc parser type to simple actual type or list of possible values
* @param {{ names: string[] }} type type from jsdoc
* @returns string[] | string
*/
const attrType = ({ names } = {}) => {
if (!names) return undefined;
// boolean values are actually string literals "true" or "false" in config
if (names[0] === "boolean") return ["true", "false"];
return names.length > 1 ? names : names[0];
};

// header with tag info and autogenerated order
// don't touch whitespaces
const infoHeader = (name, group, isNew = false, meta = {}) =>
Expand All @@ -36,6 +65,10 @@ const infoHeader = (name, group, isNew = false, meta = {}) =>
const args = process.argv.slice(2);
const outputDirArg = args[0] || `${__dirname}/../docs`;
const outputDir = path.resolve(outputDirArg);
const schemaJsonPath = args[1] || process.env.SCHEMA_JSON_PATH;

// schema for CodeMirror autocomplete
const schema = {};

fs.mkdirSync(outputDir, { recursive: true });

Expand All @@ -62,6 +95,24 @@ fetch(currentTagsUrl)
: {};
const header = supertag ? `## ${t.name}\n\n` : infoHeader(t.name, dir, isNew, meta);

// generate tag details + all attributes
schema[t.name] = {
name: t.name,
description: t.description,
attrs: Object.fromEntries(
t.params?.map((p) => [
p.name,
{
name: p.name,
description: p.description,
type: attrType(p.type),
required: !p.optional,
default: p.defaultvalue,
},
]) ?? [],
),
};

// we can use comma-separated list of @regions used by tag
const regions = t.customTags && t.customTags.find((desc) => desc.tag === "regions");
// sample regions result and description
Expand Down Expand Up @@ -157,5 +208,12 @@ fetch(currentTagsUrl)
fs.writeFileSync(path.resolve(outputDir, `${name}.md`), str);
}
}

if (schemaJsonPath) {
// @todo we can't generate correct children for every tag for some reason
// so for now we only specify children for the only root tag — View
schema.View.children = Object.keys(schema).filter((name) => name !== "!top");
fs.writeFileSync(schemaJsonPath, JSON.stringify(schema, null, 2));
}
})
.catch(console.error);
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,6 @@
background-color: var(--point-color);
}

&_timeline &__point {
width: 3px;
height: 20px;
transform: translate3d(-100%, -50%, 0);
}

&_timeline &__point:last-child {
transform: translate3d(0, -50%, 0);
}

&_timeline &__point:first-child:last-child {
width: 6px;
transform: translate3d(-50%, -50%, 0);
}

&__actions {
display: flex;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export const Keypoints: FC<KeypointsProps> = ({ idx, region, startOffset, render
return clamp(seekOffset + visibleWidth + extraSteps, 0, length);
}, [seekOffset, visibleWidth, extraSteps, length]);

const firtsPoint = sequence[0];
const start = firtsPoint ? firtsPoint.frame - 1 : 0;
const offset = firtsPoint ? start * step : startOffset;
const firstPoint = sequence[0];
const start = firstPoint ? firstPoint.frame - 1 : 0;
const offset = firstPoint ? start * step : startOffset;

const styles = useMemo(
() => ({
Expand Down
1 change: 1 addition & 0 deletions web/libs/editor/src/regions/TimelineRegion.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const TimelineRange = types.model("TimelineRange", {
});

// convert range to internal video timeline format
// it's used in `flatMap()`, so it can return both object and array of objects
function rangeToSequence(range) {
const { start, end } = range;

Expand Down
2 changes: 0 additions & 2 deletions web/libs/editor/src/tags/control/TimelineLabels.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import { HtxLabels, LabelsModel } from "./Labels/Labels";
*
* First, select a label and then click once to annotate a single frame. Click and drag to annotate multiple frames.
*
* To move forward and backward in the timeline without labeling, ensure that no labels are selected before you click.
*
* ![Screenshot of video with frame classification](../images/timelinelabels.png)
*
* Use with the `<Video>` control tag.
Expand Down
2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dm:unit": "nx run datamanager:unit",
"build": "NODE_ENV=production yarn ls:build",
"version:libs": "nx run-many --target=version",
"docs": "nx run-many --target=docs",
"docs": "SCHEMA_JSON_PATH=$PWD/apps/labelstudio/src/pages/CreateProject/Config/schema.json; nx docs editor && biome check --apply $SCHEMA_JSON_PATH",
"watch": "NODE_ENV=development BUILD_NO_SERVER=true yarn ls:watch",
"dev": "NODE_ENV=development BUILD_NO_SERVER=true yarn ls:dev",
"test:e2e": "yarn ls:e2e && yarn lsf:e2e",
Expand Down
Loading