Merge pull request 'Update to Profectus 0.7' (#13) from feat/boards into main
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
Reviewed-on: #13
This commit is contained in:
commit
bac570e78f
102 changed files with 4287 additions and 3755 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,4 +4,4 @@ docs/.vitepress/dist
|
|||
docs/.vitepress/cache
|
||||
docs/api
|
||||
components
|
||||
typedoc-theme
|
||||
typedoc-sidebar.json
|
128
docgen.config.js
128
docgen.config.js
|
@ -9,7 +9,7 @@ module.exports = {
|
|||
jsx: true // tell vue-docgen-api that your components are using JSX to avoid conflicts with TypeScript <type> syntax
|
||||
},
|
||||
getDestFile: (file, config) =>
|
||||
path.join(config.outDir, file).replace(/\.vue$/, 'Component.md'), // specify the name of the output md file
|
||||
path.join(config.outDir, file).replace(/\.vue$/, '.md'), // specify the name of the output md file
|
||||
templates: {
|
||||
// global component template wrapping all others see #templates
|
||||
component,
|
||||
|
@ -30,59 +30,83 @@ function component(renderedUsage, // props, events, methods and slots documentat
|
|||
// attached to the component documented. It includes documentation of subcomponents
|
||||
{ isSubComponent, hasSubComponents }) {
|
||||
const { displayName, description, docsBlocks } = doc
|
||||
return `
|
||||
### ${displayName} Component
|
||||
return `# Component: ${displayName}
|
||||
|
||||
${description ? '> ' + description : ''}
|
||||
${description}
|
||||
|
||||
${renderedUsage.props}
|
||||
${renderedUsage.methods}
|
||||
${renderedUsage.events}
|
||||
${renderedUsage.slots}
|
||||
${docsBlocks?.length ? '---\n' + docsBlocks.join('\n---\n') : ''}
|
||||
`
|
||||
${renderedUsage.props}
|
||||
${renderedUsage.methods}
|
||||
${renderedUsage.events}
|
||||
${renderedUsage.slots}
|
||||
${docsBlocks?.length ? '---\n' + docsBlocks.join('\n---\n') : ''}
|
||||
|
||||
## Defined in
|
||||
|
||||
[profectus/src/${fileName}](https://code.incremental.social/profectus/Profectus/src/branch/main/src/${fileName})
|
||||
`.replaceAll('`Resource`', '[`Resource`](/api/features/resources/resource/interfaces/Resource)')
|
||||
.replaceAll('`MaybeGetter`', '[`MaybeGetter`](/api/util/computed/type-aliases/MaybeGetter)')
|
||||
.replaceAll('`Error`', '[`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)')
|
||||
.replaceAll('`MouseEvent`', '[`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)')
|
||||
.replaceAll('`TouchEvent`', '[`TouchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent)')
|
||||
.replaceAll('`Hotkey`', '[`Hotkey`](/api/features/hotkey/interfaces/Hotkey)')
|
||||
.replaceAll('`Requirements`', '[`Requirements`](/api/game/requirements/interfaces/Requirement)')
|
||||
.replaceAll('`Renderable`', '[`Renderable`](/api/util/vue/type-aliases/Renderable)')
|
||||
.replaceAll('`Direction`', '[`Direction`](/api/util/common/enumerations/Direction)')
|
||||
.replaceAll('`DecimalSource`', '[`DecimalSource`](/api/util/bignum/type-aliases/DecimalSource)')
|
||||
.replaceAll('`Visibility`', '[`Visibility`](/api/features/feature/enumerations/Visibility)')
|
||||
.replaceAll('`Link`', '[`Link`](/api/features/links/links/interfaces/Link)')
|
||||
.replaceAll('`FeatureNode`', '[`FeatureNode`](/api/game/layers/interfaces/FeatureNode)')
|
||||
.replaceAll('`DOMRect`', '[`DOMRect`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect)')
|
||||
.replaceAll('`Tab`', '[`Tab`](/api/features/tabs/tab/interfaces/Tab)')
|
||||
.replaceAll('`TabButton`', '[`TabButton`](/api/features/tabs/tabFamily/interfaces/TabButton)')
|
||||
.replaceAll('`TreeNode`', '[`TreeNode`](/api/features/trees/tree/interfaces/TreeNode)')
|
||||
.replaceAll('`TreeBranch`', '[`TreeBranch`](/api/features/trees/tree/interfaces/TreeBranch)')
|
||||
.replaceAll('`Wrapper`', '[`Wrapper`](/api/util/vue/type-aliases/Wrapper)')
|
||||
.replaceAll('`LoadablePlayerData`', '[`LoadablePlayerData`](/api/util/save/type-aliases/LoadablePlayerData)')
|
||||
// ^ Manually created for the types I am currently noticing show up in components
|
||||
}
|
||||
|
||||
const eventCols = ["Name", "Description", "Properties"];
|
||||
function displayType(type) {
|
||||
let n = type?.name ?? type?.names?.[0] ?? '';
|
||||
if (n === "union") n = type?.elements.map(displayType).join(' | ')
|
||||
else if (n === "Array") n = (type?.elements.length > 1 ? "(" : "") + type?.elements.map(el => el.name).join(' | ') + (type?.elements.length > 1 ? ")" : "") + "[]"
|
||||
else if (n && type?.elements?.length > 0) n = `\`${n}\`<${type.elements.map(displayType).join(", ")}>`
|
||||
else n = `\`${n}\``
|
||||
return n;
|
||||
}
|
||||
|
||||
const eventCols = ["Name", "Description", "Parameter", "Return"];
|
||||
const eventShouldDisplayCol = {
|
||||
[eventCols[0]]: event => event.name,
|
||||
[eventCols[1]]: event => event.description,
|
||||
[eventCols[2]]: event => event.properties?.length
|
||||
[eventCols[1]]: event => true,
|
||||
[eventCols[2]]: event => event.type,
|
||||
[eventCols[3]]: event => true
|
||||
}
|
||||
const eventGetDisplay = {
|
||||
[eventCols[0]]: event => `<code>${clean(event.name)}</code>`,
|
||||
[eventCols[1]]: event => clean(event.description || ""),
|
||||
[eventCols[2]]: event => {
|
||||
if (!event.properties) return "";
|
||||
return event.properties.map(property => {
|
||||
const { name, description, type } = property
|
||||
if (!type) {
|
||||
return ''
|
||||
}
|
||||
return `**${name}** <code>${clean(type.names.length ? type.names.join(', ') : '')}</code> - ${description}`
|
||||
}).join('<br/>')
|
||||
}
|
||||
[eventCols[0]]: event => `${clean(event.name)}()`,
|
||||
[eventCols[1]]: event => `> **${clean(event.name)}**: (${event.type ? `\`parameter\`` : ''}) => \`void\`\n\n${clean(event.description || "")}`,
|
||||
[eventCols[2]]: event => event.type ? `#### Parameters\n\n\n##### parameter\n\n\n${displayType(event.type)}` : '',
|
||||
[eventCols[3]]: event => `#### Returns\n\n\n\`void\``
|
||||
}
|
||||
|
||||
const propCols = ["Name", "Type", "Description", "Values", "Default"];
|
||||
const propCols = ["Name", "Type", "Description", "Values"/*, "Default"*/];
|
||||
const propShouldDisplayCol = {
|
||||
[propCols[0]]: pr => pr.name,
|
||||
[propCols[1]]: pr => pr.type?.name,
|
||||
[propCols[2]]: pr => pr.description || pr.tags,
|
||||
[propCols[3]]: pr => pr.values,
|
||||
[propCols[4]]: pr => pr.defaultValue
|
||||
// [propCols[4]]: pr => pr.defaultValue
|
||||
}
|
||||
const propGetDisplay = {
|
||||
[propCols[0]]: pr => `<code>${clean(pr.name)}</code>` + (pr.required ? "*" : ""),
|
||||
[propCols[0]]: pr => clean(pr.name) + (pr.required ? "" : "?"),
|
||||
[propCols[1]]: pr => {
|
||||
let n = pr.type?.name ?? ''
|
||||
if (n === "union") n = pr.type?.elements.map(el => el.name).join(' | ')
|
||||
if (n === "Array") n = (pr.type?.elements.length > 1 ? "(" : "") + pr.type?.elements.map(el => el.name).join(' | ') + (pr.type?.elements.length > 1 ? ")" : "") + "[]"
|
||||
return n ? '<code>' + clean(n) + '</code>' : ''
|
||||
if (!pr.type) return '';
|
||||
let n = displayType(pr.type);
|
||||
return `> ${clean(n)}`;
|
||||
},
|
||||
[propCols[2]]: pr => clean((pr.description ?? '') + renderTags(pr.tags)),
|
||||
[propCols[3]]: pr => clean(pr.values?.map(pv => `\`${pv}\``).join(', ') ?? '-'),
|
||||
[propCols[4]]: pr => clean(pr.defaultValue?.value ?? '')
|
||||
// [propCols[4]]: pr => clean(pr.defaultValue?.value ?? '')
|
||||
}
|
||||
|
||||
const slotCols = ["Name", "Description", "Bindings"];
|
||||
|
@ -92,21 +116,15 @@ const slotShouldDisplayCol = {
|
|||
[slotCols[2]]: slot => slot.bindings?.length
|
||||
}
|
||||
const slotGetDisplay = {
|
||||
[slotCols[0]]: slot => `<code>${clean(slot.name)}</code>`,
|
||||
[slotCols[0]]: slot => clean(slot.name),
|
||||
[slotCols[1]]: slot => clean(slot.description || ""),
|
||||
[slotCols[2]]: slot => {
|
||||
if (!slot.bindings) return "";
|
||||
return slot.bindings.map(binding => {
|
||||
const { name, description, type } = binding
|
||||
if (!type) {
|
||||
return ''
|
||||
}
|
||||
return `**${name}** <code>${
|
||||
type.name === 'union' && type.elements
|
||||
? type.elements.map(({ name: insideName }) => insideName).join(', ')
|
||||
: type.name
|
||||
}</code> - ${description}`
|
||||
}).join('<br/>')
|
||||
if (!slot.bindings) return '';
|
||||
let output = `#### Bindings\n`;
|
||||
slot.bindings.forEach(({ name, description, type }) => {
|
||||
output += `\n##### ${name}\n\n\n${displayType(type)}\n\n\n${description}`;
|
||||
});
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,16 +132,14 @@ function displayUsedCols(title, cols, shouldDisplayCol, getDisplay, rows, opt) {
|
|||
cols = cols.filter(col => rows.some(shouldDisplayCol[col]));
|
||||
if (!cols) return "";
|
||||
|
||||
let output = `\n${opt.isSubComponent || opt.hasSubComponents ? '#' : ''}#### ${title}\n|`;
|
||||
cols.forEach(col => (output += ` ${col} |`));
|
||||
output += `\n|`;
|
||||
cols.forEach(col => (output += ' :-- |'));
|
||||
output += '\n';
|
||||
|
||||
rows.forEach(row => {
|
||||
output += "|";
|
||||
cols.forEach(col => (output += ` ${getDisplay[col](row)} |`));
|
||||
output += "\n";
|
||||
let output = `\n${opt.isSubComponent || opt.hasSubComponents ? '#' : ''}## ${title}\n`;
|
||||
rows.forEach((row, i) => {
|
||||
const [name, ...other] = cols;
|
||||
if (i > 0) output += "\n***\n";
|
||||
output += `\n${opt.isSubComponent || opt.hasSubComponents ? '#' : ''}### ${getDisplay[name](row)}\n`;
|
||||
other.forEach(col => {
|
||||
output += `\n ${getDisplay[col](row)}\n`;
|
||||
});
|
||||
});
|
||||
|
||||
return output;
|
||||
|
|
|
@ -11,7 +11,6 @@ module.exports = {
|
|||
['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' }],
|
||||
['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' }],
|
||||
['link', { rel: 'manifest', href: '/site.webmanifest' }],
|
||||
['script', { defer: true, 'data-domain': 'moddingtree.com', src: 'https://plausible.io/js/plausible.js' }],
|
||||
['meta', { name: 'og:description', content: 'A game engine that grows with you' }],
|
||||
['meta', { name: 'og:image', content: '/Logo.png' }]
|
||||
],
|
||||
|
@ -21,12 +20,12 @@ module.exports = {
|
|||
themeConfig: {
|
||||
logo: "/favicon.svg",
|
||||
editLink: {
|
||||
pattern: "https://code.incremental.social/profectus/profectus-docs/edit/main/docs/:path",
|
||||
pattern: "https://code.incremental.social/profectus/profectus-docs/_edit/main/docs/:path",
|
||||
editLinkText: "Edit this page on Incremental Social"
|
||||
},
|
||||
nav: [
|
||||
{ text: "Guide", link: "/guide/", activeMatch: "^/guide/" },
|
||||
{ text: "API", link: "/api/overview", activeMatch: "^/api/" },
|
||||
{ text: "API", link: "/api", activeMatch: "^/api/" },
|
||||
{ text: "Forums", link: "https://forums.moddingtree.com" }
|
||||
],
|
||||
socialLinks: [
|
||||
|
@ -64,7 +63,6 @@ module.exports = {
|
|||
items: [
|
||||
{ text: "Layers", link: "/guide/important-concepts/layers" },
|
||||
{ text: "Features", link: "/guide/important-concepts/features" },
|
||||
{ text: "Coercable Components", link: "/guide/important-concepts/coercable" },
|
||||
{ text: "Reactivity", link: "/guide/important-concepts/reactivity" },
|
||||
{ text: "Persistence", link: "/guide/important-concepts/persistence" },
|
||||
{ text: "Requirements", link: "/guide/important-concepts/requirements" },
|
||||
|
@ -77,15 +75,18 @@ module.exports = {
|
|||
items: [
|
||||
{ text: "Prestige Mechanic", link: "/guide/recipes/prestige" },
|
||||
{ text: "Display Save Progress", link: "/guide/recipes/save-progress" },
|
||||
{ text: "Display Particle Effect", link: "/guide/recipes/particles" }
|
||||
{ text: "Display Particle Effect", link: "/guide/recipes/particles" },
|
||||
{ text: "Resources on Forums", link: "https://forums.moddingtree.com/tags/c/modding-help/resources/17/profectus" }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "Advanced Concepts",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: "Boards", link: "/guide/advanced-concepts/boards" },
|
||||
{ text: "Creating Features", link: "/guide/advanced-concepts/creating-features" },
|
||||
{ text: "Dynamic Layers", link: "/guide/advanced-concepts/dynamic-layers" },
|
||||
{ text: "Mixins and Wrappers", link: "/guide/advanced-concepts/mixins" },
|
||||
{ text: "Nodes", link: "/guide/advanced-concepts/nodes" }
|
||||
]
|
||||
},
|
||||
|
@ -93,75 +94,22 @@ module.exports = {
|
|||
text: "Migrations",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "0.5.X to 0.6.0", link: "/guide/migrations/0-6" }
|
||||
{ text: "0.5.X to 0.6.0", link: "/guide/migrations/0-6" },
|
||||
{ text: "0.6.X to 0.7.0", link: "/guide/migrations/0-7" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"/api/": generateAPISidebar()
|
||||
}
|
||||
},
|
||||
markdown: {
|
||||
theme: {
|
||||
light: "material-theme-palenight",
|
||||
dark: "material-theme-palenight"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateAPISidebar() {
|
||||
const sidebar = [];
|
||||
|
||||
const modules = fs.readdirSync("./docs/api/modules");
|
||||
modules.forEach(file => {
|
||||
const moduleSidebar = { text: camelToTitle(file), items: [], collapsed: file === "lib" };
|
||||
sidebar.push(moduleSidebar)
|
||||
walk(path.join("./docs/api/modules", file), moduleSidebar.items);
|
||||
});
|
||||
|
||||
const componentFolders = [];
|
||||
walk("./docs/api/components", componentFolders);
|
||||
sidebar.unshift({
|
||||
text: "Components",
|
||||
collapsed: true,
|
||||
items: componentFolders
|
||||
});
|
||||
|
||||
sort(sidebar);
|
||||
|
||||
return sidebar;
|
||||
}
|
||||
|
||||
function sort(sidebar) {
|
||||
sidebar.filter(sidebar => !!sidebar.items).forEach(item => sort(item.items));
|
||||
sidebar.sort((a, b) => {
|
||||
if (a.items && !b.items) {
|
||||
return -1;
|
||||
} else if (!a.items && b.items) {
|
||||
return 1;
|
||||
} else if (a.text > b.text) {
|
||||
return 1;
|
||||
} else if (a.text < b.text) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function walk(dir, sidebar) {
|
||||
const files = fs.readdirSync(dir);
|
||||
files.forEach(file => {
|
||||
const resolvedFile = path.join(dir, file);
|
||||
const stat = fs.statSync(resolvedFile);
|
||||
if (stat.isDirectory()) {
|
||||
const subSidebar = { text: camelToTitle(file), items: [], collapsed: true };
|
||||
sidebar.push(subSidebar);
|
||||
walk(resolvedFile, subSidebar.items);
|
||||
} else if (!file.includes("Component") || dir.includes("components")) {
|
||||
sidebar.push({ text: camelToTitle(file.substr(0, file.length - 3)), link: "/" + resolvedFile.substr(5, resolvedFile.length - 8).replace(/\\/g, "/") + ".html" });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function camelToTitle(camel) {
|
||||
if (camel === "break_eternity") {
|
||||
return "Break Eternity";
|
||||
}
|
||||
let title = camel.replace(/([A-Z])/g, " $1");
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
return title;
|
||||
return JSON.parse(fs.readFileSync("./typedoc-sidebar.json").toString().replaceAll("..\\\\docs\\\\", ""));
|
||||
}
|
||||
|
|
|
@ -56,3 +56,11 @@ body {
|
|||
.VPSocialLink > svg {
|
||||
width: 30px !important;
|
||||
}
|
||||
|
||||
.VPBadge {
|
||||
color: var(--vp-badge-info-text) !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
border-bottom: none !important;
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
--vp-c-brand-3: #5E81AC;
|
||||
--vp-c-brand-light: #81A1C1;
|
||||
--vp-c-brand-dark: #81A1C1;
|
||||
--vp-c-gutter: #3B4252;
|
||||
--vp-button-brand-border: #81A1C1;
|
||||
--vp-button-brand-text: #ECEFF4;
|
||||
--vp-button-brand-hover-text: #ECEFF4;
|
||||
|
|
189
docs/guide/advanced-concepts/boards.md
Normal file
189
docs/guide/advanced-concepts/boards.md
Normal file
|
@ -0,0 +1,189 @@
|
|||
# Boards
|
||||
|
||||
The Board component allows you to make a pannable and zoomable "board" of components, called nodes. Instead of laying things out using the DOM, everything inside a board should be absolutely positioned. There are various utilities included in [board.tsx](/api/game/boards/board) to assist with implementing common behaviors with boards. Also, most of these code snippets are modified from [the demo project](https://code.incremental.social/profectus/Profectus-Demo/src/branch/main/src/data/layers/board.tsx), which may make a useful reference while implementing your own boards.
|
||||
|
||||
To get started with a board, with a node that's just an upgrade locked to a specific location, it would look like this:
|
||||
|
||||
```ts
|
||||
const upgrade = createUpgrade({
|
||||
class: "board-node",
|
||||
style: { transform: "translate(100px, 100px)" },
|
||||
/** snip **/
|
||||
});
|
||||
|
||||
/** snip **/
|
||||
|
||||
// Render function
|
||||
() => (<Board>
|
||||
{render(upgrade)}
|
||||
</Board>);
|
||||
```
|
||||
|
||||
## Selecting Nodes
|
||||
|
||||
There is a common (not Board specific) utility for creating a ref, along with a setter and "clearer" that works perfectly for situations where you'd like to let the player select at most 1 of a group of like things. To use it, you'll want to clear the selection on mouse down on either a node or the Board itself, and set the selection on mouse up on a selectable node.
|
||||
|
||||
Note you'll typically want to store an ID rather than the node itself, so that if you make the selection persistent you can still easily determine which node was chosen, even if all their other properties are identical. The easiest way to get guaranteed unique IDs for every node is to include an `id` property on every node and use the [setupUniqueIds](/api/game/boards/board/functions/setupUniqueIds) utility which will give you a ref with the value of a valid unique ID you can use for any newly created node.
|
||||
|
||||
## Dragging Nodes
|
||||
|
||||
Draggable nodes are substantially more complicated. Ultimately you'll want to use either [setupDraggableNode](/api/game/boards/board/functions/setupDraggableNode) and, if applicable, [makeDraggable](/api/game/boards/board/functions/makeDraggable), but from there hooking everything up is still a fairly manual process.
|
||||
|
||||
Similar to selecting nodes, draggable nodes should also typically be based on IDs. Assuming your nodes have x and y properties for their actual position, your `setupDraggableNode` call should look something like this:
|
||||
|
||||
```ts
|
||||
const board = ref<ComponentPublicInstance<typeof Board>>();
|
||||
|
||||
// You can use any other method to map IDs to the actual nodes
|
||||
const nodesById = computed<Record<string, NodePosition>>(() =>
|
||||
nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {})
|
||||
);
|
||||
|
||||
const { startDrag, endDrag, drag, nodeBeingDragged, hasDragged, dragDelta } =
|
||||
setupDraggableNode<number>({
|
||||
board,
|
||||
getPosition(id) {
|
||||
return nodesById.value[id];
|
||||
},
|
||||
setPosition(id, position) {
|
||||
const node = nodesById.value[id];
|
||||
node.x = position.x;
|
||||
node.y = position.y;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The type hints in your IDE should clarify how to hook up or use each of the returned properties, but we'll go over them here as well. On mouse down on a draggable node, call `startDrag`. Whenever the mouse goes back up or leaves the board, call `endDrag`. And `drag` itself gets called when the mouse is moved over the board. In all, your Board element should look like this, including the ref property used to pass the component to `setupDraggableNode`:
|
||||
|
||||
```ts
|
||||
<Board onDrag={drag} onMouseUp={endDrag} ref={board}>
|
||||
```
|
||||
|
||||
Note that if you'd like to have nodes be both draggable _and_ selectable, you should also include `onMouseDown={deselect}`. Your mouse up and down listeners on the nodes themselves should look something like this:
|
||||
|
||||
```ts
|
||||
function mouseDown(e: MouseEvent | TouchEvent, node: MyNodeType) {
|
||||
if (nodeBeingDragged.value == null) {
|
||||
startDrag(e, node.id);
|
||||
}
|
||||
deselect();
|
||||
}
|
||||
function mouseUp(e: MouseEvent | TouchEvent, node: MyNodeType) {
|
||||
if (!hasDragged.value) {
|
||||
endDrag();
|
||||
select(node.id);
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When dragging, you'll often want to ensure that the node being dragged is always on top of all the others, and when you stop dragging it doesn't just jump down several layers. Rather than re-ordering the elements, which can break CSS transitions, it's recommended to specify the z index of each node. An easy way to do that is by setting the initial z index of any new node to its ID. Then on mouse down update the z indices so that the current node being dragged shifts all nodes between it and the "top" z index. Keep in mind this assumes the indices are contiguous, which you can check for and ensure when removing nodes.
|
||||
|
||||
```ts
|
||||
const oldZ = node.z;
|
||||
nodes.value.forEach(node => {
|
||||
if (node.z > oldZ) {
|
||||
node.z--;
|
||||
}
|
||||
});
|
||||
node.z = nextId.value;
|
||||
```
|
||||
|
||||
Now to handle rendering the node being dragged appropriately. If they're being dragged, you'll want to account for that drag when calculating the position to render at. Check if the node is being dragged, and if so add the `dragDelta` components to its position. To that end, you might write a function like this to get the translate component of a CSS transform rule:
|
||||
|
||||
```ts
|
||||
function translate(node: NodePosition, isDragging: boolean) {
|
||||
let x = node.x;
|
||||
let y = node.y;
|
||||
if (isDragging) {
|
||||
x += dragDelta.value.x;
|
||||
y += dragDelta.value.y;
|
||||
}
|
||||
return ` translate(${x}px,${y}px)`;
|
||||
}
|
||||
```
|
||||
|
||||
Finally, if you have any existing features that you'd like to make draggable, the `makeDraggable` utility lets you do just that so you don't have to worry about not being able to directly hook onto the feature's mouse down or up listeners and other configuration. It typically just requires passing along several of the properties you got from the `setupDraggableNode` call, plus some additional callbacks to allow for still retaining the original feature's interactivity. Here's an example of making an Upgrade feature draggable:
|
||||
|
||||
```ts
|
||||
makeDraggable(upgrade, {
|
||||
id: "my-upgrade-id",
|
||||
endDrag,
|
||||
startDrag,
|
||||
hasDragged,
|
||||
nodeBeingDragged,
|
||||
dragDelta,
|
||||
onMouseUp: upgrade.purchase
|
||||
});
|
||||
```
|
||||
|
||||
## Dropping Nodes onto Other Nodes
|
||||
|
||||
To support dropping nodes onto other nodes, you'll need to provide up to 3 new fields to `setupDraggableNode`: `receivingNodes`, the list of all nodes that the currently dragged node can be dropped upon; `dropAreaRadius`, the radius of the circular drop zone around each receiving node; and of course `onDrop`, the actual callback for when a node gets dropped on a receiving node.
|
||||
|
||||
You'll then typically want to use the returned `receivingNode` and `receivingNodes` properties to display some indicator that anything in `receivingNodes` can receive the currently dragged node, and that if they let their mouse go right now, it'd drop into `receivingNode` specifically. In all, that might look something like this, as part of the node's display:
|
||||
|
||||
```ts
|
||||
{receivingNodes.value.includes(node.id) && (
|
||||
<circle
|
||||
r="50"
|
||||
fill="var(--background)"
|
||||
stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"}
|
||||
stroke-width="2"
|
||||
/>
|
||||
)}
|
||||
```
|
||||
|
||||
## Common Display Components
|
||||
|
||||
The board system is intended to be very generic, allowing you to make whatever sort of components you like. Of particular note, SVG components are first-class citizens and incredibly easy to design completely custom displays with (and is what this section will be focused on since rendering vue features is straightforward). There's a utility component called SVGNode that will handle the positioning and listening to both touch and mouse events for you. Any SVG elements should go inside one of these.
|
||||
|
||||
Keep in mind, for performance reasons, it may be beneficial to put many elements in one `SVGNode`, particularly if they don't need to use the event handlers. For example, examples like this where you're rendering lines connecting various nodes:
|
||||
|
||||
```ts
|
||||
const links = () => (
|
||||
<>
|
||||
{nodes.value
|
||||
.reduce(
|
||||
(acc, curr) => [
|
||||
...acc,
|
||||
// Replace this with your own logic for determining links to draw
|
||||
...curr.links.map(l => ({ from: curr, to: nodesById.value[l] }))
|
||||
],
|
||||
[] as { from: NodePosition; to: NodePosition }[]
|
||||
)
|
||||
.map(link => (
|
||||
<line
|
||||
stroke="white"
|
||||
stroke-width={4}
|
||||
// Note how we handle adding dragDelta to the node being dragged. You may consider writing a utility function to help with this process
|
||||
x1={
|
||||
nodeBeingDragged.value === link.from.id
|
||||
? dragDelta.value.x + link.from.x
|
||||
: link.from.x
|
||||
}
|
||||
y1={
|
||||
nodeBeingDragged.value === link.from.id
|
||||
? dragDelta.value.y + link.from.y
|
||||
: link.from.y
|
||||
}
|
||||
x2={
|
||||
nodeBeingDragged.value === link.to.id
|
||||
? dragDelta.value.x + link.to.x
|
||||
: link.to.x
|
||||
}
|
||||
y2={
|
||||
nodeBeingDragged.value === link.to.id
|
||||
? dragDelta.value.y + link.to.y
|
||||
: link.to.y
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
// And then in the render function:
|
||||
<SVGNode>{links()}</SVGNode>
|
||||
```
|
||||
|
||||
Beyond that, just use the standard SVG elements like `rect`, `circle`, `text`, etc. to fully design your nodes. There's also a few other built-in utility components like `CircularProgress` that do common but complex display parts. You may also use the global `node-text` CSS class to make the default `text` elements appear larger and centered.
|
|
@ -4,17 +4,21 @@ Profectus is designed to encourage the developer to eventually start designing t
|
|||
|
||||
## Creating the Feature
|
||||
|
||||
Every feature has a couple of types. They have the feature themselves, a generic version for convenience, and any constructor typically has an options type and a type that gets "added" to it to create the feature itself. These typically involve replacing some types to denote how various properties change from, for example, `Computable<X>` to `ProcessedComputable<X>`. You should be able to use any of the existing features as a reference for how these types look and work.
|
||||
Every feature has an interface for the feature itself as well as an options object for every constructor the feature has (typically just one). The main difference is that the options object will have computable properties typed as `MaybeRefOrGetter` but the feature itself will have replaced those with just `MaybeRef`. You should be able to use any of the existing features as a reference for how these types look and work.
|
||||
|
||||
Most significantly, the base type should typically always have a `type` property pointing to a symbol unique to this feature, so they can be easily differentiated at runtime. If it's a feature that should be renderable, then it'll also need `[Component]` and `[GatherProps]` properties, which describe the vue component to use and how to get the props for it from this feature, as well as a unique ID for the feature's [node](./nodes). You can use the [getUniqueID](/api/modules/features/feature#getuniqueid) utility to help.
|
||||
The feature should typically have a `type` property pointing to a symbol unique to this feature, so they can be easily differentiated at runtime. If it's a feature that should be renderable, then it'll also need to make sure the options extends `VueFeatureOptions` and the feature itself `VueFeature`. These will handle allowing it to be rendered using the `render` utility function and handle things like style, classes, visibility, a unique ID, and adding its [node](./nodes) to the layer or modal's context.
|
||||
|
||||
The constructor itself should do several things. They should take their options within a function, so that they're not resolved when the object is constructed. It should return a lazy proxy of the feature, which allows features to reference each other and only resolve themselves once every feature is defined. The constructor should create any persistent refs it may require outside of the lazy proxy - it won't have access to the options at this point, so it should make any it _potentially_ may require. Any that turn out not being needed can be [deleted](/api/modules/game/persistence#deletepersistent). Inside the lazy proxy the constructor should create the options object, assign onto it every property from the base type, call [processComputable](/api/modules/util/computed#processcomputable) on every computable type, and [setDefault](/api/modules/features/feature#setdefault) on any property with a default value. Then you should be able to simply return the options object, likely with a type cast, and the constructor will be complete.
|
||||
The constructor itself should do several things. They should take their options within a function, so that they're not resolved when the object is constructed. It should return a lazy proxy of the feature, which allows features to reference each other and only resolve themselves once every feature is defined. The constructor should create any persistent refs it may require outside of the lazy proxy - it won't have access to the options at this point, so it should make any it _potentially_ may require. Any that turn out not being needed can be [deleted](/api/game/persistence/functions/deletePersistent). Inside the lazy proxy the constructor should create the feature object, including the extra properties in the options object, vue feature mixin, and use [processGetter](/api/util/computed/functions/processGetter) on every computable type. You should ensure the feature object `satisfies` the feature interface, and then return it.
|
||||
|
||||
Because typescript does not emit JS, if a property is supposed to be a function it is impossible to differentiate between a function that is itself the intended value or a function that returns the actual value. For this reason it is not recommended for any feature types to include properties that are `Computable<Function>`s, and all functions _will_ be wrapped in `computed`. The notable exception to this is [JSX](../important-concepts/coercable#render-functions-jsx), which uses a utility function to mark that a function should not be wrapped.
|
||||
The vue feature mixin will require a string unique to the feature as well as a function to get a `JSX.Element` for this feature. Typically that just means returning the vue component made for this feature, passing in props from the feature object.
|
||||
|
||||
Because typescript does not emit JS, if a property is supposed to be a function it is impossible to differentiate between a function that is itself the intended value or a function that returns the actual value. For this reason, it is not recommended for any feature types to include properties that are `MaybeRefOrGetter<Function>`s, and all functions _will_ be wrapped in `computed`.
|
||||
|
||||
## Vue Components
|
||||
|
||||
Any vue components you write need to do a couple things. Typically they'll need to type any props that come from computed properties appropriately, for which you can use the [processedPropType](/api/modules/util/vue#processedproptype) utility - using it will look something like `style: processedPropType<StyleValue>(String, Object, Array)`. You'll also want to make sure to `unref` any of these props you use in the template. `style` is an exception though, where it should actually be unreffed inside the `GatherProps` function or else it won't work with computed refs. The template should make sure to include a `Node` component with the feature's ID. Also, if there are custom displays this feature may have, you'll want to convert the `CoercableComponent` into a Vue component inside the setup function, typically using the [computeComponent](/api/modules/util/vue#computecomponent) or [computeOptionalComponent](/api/modules/util/vue#computeoptionalcomponent) utilities.
|
||||
Any vue components you write need to do a couple things. Typically they'll need to type each prop directly, but you can just type it as the property on the feature itself. That is, you can't do `defineProps<MyFeature>()` but you can do `defineProps<{ display: MyFeature["display"]; }>()`. If there are custom displays this feature may have, you'll want to create a PascalCase variable that is just a function passing the prop into the [render](/api/util/vue/functions/render) utility, like this:
|
||||
|
||||
`const Title = () => render(props.title);`
|
||||
|
||||
## Custom Settings
|
||||
|
||||
|
@ -28,26 +32,26 @@ declare module "game/settings" {
|
|||
}
|
||||
```
|
||||
|
||||
Next you must set the default value of this setting when the settings is loaded, which happens during the `loadSettings` event emitted on the [global bus](/api/modules/game/events#globalbus). This is how that looks like for the same `hideChallenges` setting from above:
|
||||
Next you must set the default value of this setting when the settings is loaded, which happens during the `loadSettings` event emitted on the [global bus](/api/game/events/variables/globalBus). This is how that looks like for the same `hideChallenges` setting from above:
|
||||
|
||||
```ts
|
||||
globalBus.on("loadSettings", settings => {
|
||||
setDefault(settings, "hideChallenges", false);
|
||||
settings.hideChallenges ??= false;
|
||||
});
|
||||
```
|
||||
|
||||
Finally, you'll need to register a Vue component to the settings menu so the player can actually modify this setting. Here's the example for the `hideChallenges` setting:
|
||||
|
||||
```ts
|
||||
registerSettingField(
|
||||
jsx(() => (
|
||||
globalBus.on("setupVue", () =>
|
||||
registerSettingField(() => (
|
||||
<Toggle
|
||||
title={jsx(() => (
|
||||
title={
|
||||
<span class="option-title">
|
||||
Hide maxed challenges
|
||||
<desc>Hide challenges that have been fully completed.</desc>
|
||||
</span>
|
||||
))}
|
||||
}
|
||||
onUpdate:modelValue={value => (settings.hideChallenges = value)}
|
||||
modelValue={settings.hideChallenges}
|
||||
/>
|
||||
|
@ -57,12 +61,12 @@ registerSettingField(
|
|||
|
||||
## Updating Features
|
||||
|
||||
If your custom feature requires running some sort of update method every tick, you'll want to search layers when they're added for any features of this type (using the [findFeatures](/api/modules/features/feature#findfeatures) utility), add an event handler for every `update`/`postUpdate`/`preUpdate`, and clean it up when the layer is removed. Here's how this looks like for the `action` feature:
|
||||
If your custom feature requires running some sort of update method every tick, you'll want to search layers when they're added for any features of this type (using the [findFeatures](/api/features/feature/functions/findFeatures) utility), add an event handler for every `update`/`postUpdate`/`preUpdate`, and clean it up when the layer is removed. Here's how this looks like for the `action` feature:
|
||||
|
||||
```ts
|
||||
const listeners: Record<string, Unsubscribe | undefined> = {};
|
||||
globalBus.on("addLayer", layer => {
|
||||
const actions: GenericAction[] = findFeatures(layer, ActionType) as GenericAction[];
|
||||
const actions: Action[] = findFeatures(layer, ActionType) as Action[];
|
||||
listeners[layer.id] = layer.on("postUpdate", diff => {
|
||||
actions.forEach(action => action.update(diff));
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Dynamic Layers
|
||||
|
||||
You can dynamically add and remove layers using the [addLayer](/api/modules/game/layers#addlayer) and [removeLayer](/api/modules/game/layers#removelayer) functions. It's important to note that removing a layer does not affect the player's save data. You can safely add and remove the same layer without losing any progress. For instances where the structure of a layer changes, such as when adding a new feature, use the [reloadLayer](/api/modules/game/layers#reloadlayer) function.
|
||||
You can dynamically add and remove layers using the [addLayer](/api/game/layers/functions/addLayer) and [removeLayer](/api/game/layers/functions/removeLayer) functions. It's important to note that removing a layer does not affect the player's save data. You can safely add and remove the same layer without losing any progress. For instances where the structure of a layer changes, such as when adding a new feature, use the [reloadLayer](/api/game/layers/functions/reloadLayer) function.
|
||||
|
||||
When procedurally generating layers with similar structures, consider using a utility function like the one below. This function allows you to access a correctly typed reference to a layer with a specified ID easily:
|
||||
|
||||
|
@ -8,10 +8,10 @@ When procedurally generating layers with similar structures, consider using a ut
|
|||
function getDynLayer(id: string): DynamicLayer {
|
||||
const layer = layers[id];
|
||||
if (!layer) throw "Layer does not exist";
|
||||
return layer as DynamicLayer; // you might need an "as unknown" after layer
|
||||
return layer as DynamicLayer;
|
||||
}
|
||||
```
|
||||
|
||||
This utility function can streamline your code when dealing with multiple dynamic layers and ensure that you're working with the correct layer type.
|
||||
|
||||
When working with dynamic layers you'll need to ensure you can determine what layers should exist when loading a save file, by returning an accurate list from your project's [getInitialLayers](/api/modules/data/projEntry#getinitiallayers) function.
|
||||
When working with dynamic layers you'll need to ensure you can determine what layers should exist when loading a save file, by returning an accurate list from your project's [getInitialLayers](/api/data/projEntry/functions/getInitialLayers) function.
|
||||
|
|
36
docs/guide/advanced-concepts/mixins.md
Normal file
36
docs/guide/advanced-concepts/mixins.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Mixins and Wrappers
|
||||
|
||||
Mixins and wrappers are ways of adding functionality to your features in a modular way, allowing them to be shared with the community or reused between projects with ease. There's already a couple built into the engine, including one that every renderable feature uses, `vueFeatureMixin`.
|
||||
|
||||
## Mixins
|
||||
|
||||
Mixins are for adding additional properties to the feature, and are used by the one writing the feature itself (rather than just instantiating it). For example, `vueFeatureMixin` takes a couple parameters - a string for identifying features, an options object that can contain settings like `visibility`, `style`, `classes`, etc. - and a render function - and adds various properties required for rendering the feature. The mixin gets implemented by having the feature's options object extending the mixin's options, the feature interface extending the mixin itself, and destructuring the mixin's return object in the constructor itself:
|
||||
|
||||
```ts
|
||||
const clickable = {
|
||||
type: ClickableType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof ClickableOptions>),
|
||||
...vueFeatureMixin("clickable", options, () => (
|
||||
<Clickable
|
||||
canClick={clickable.canClick}
|
||||
onClick={clickable.onClick}
|
||||
display={clickable.display}
|
||||
/>
|
||||
)),
|
||||
...
|
||||
} satisfies Clickable;
|
||||
```
|
||||
|
||||
You'll note the properties included by the mixin also get omitted from the props object, since they'll be overwritten. In the end, you'll know its setup correctly because the `satisfies` clause won't cause issues (assuming you've correctly made `Clickable` extend `VueFeature` or whatever the name of the feature and mixin are).
|
||||
|
||||
Custom mixins should work similarly to the existing mixins and support everything mentioned above. The bonus amounts/completions mixins are good examples of what a simpler mixin would look like.
|
||||
|
||||
If there's a feature you'd like to write that could work as either a mixin or wrapper, prefer mixins due to their better typing. If the person using the mixin or wrapper wasn't creating a new feature, they can easily extend the feature to use the mixin like I demonstrate [here](https://forums.moddingtree.com/t/using-the-bonus-mixins-on-repeatables-and-challenges/1648).
|
||||
|
||||
## Wrappers
|
||||
|
||||
Wrappers have the advantage of not requiring extending the feature and being able to access pre-defined properties on the feature, but make types a bit more tricky to work with.
|
||||
|
||||
Wrappers take a constructed feature and modify or add properties on it. For example, `addTooltip` will take a vue feature and make it have a tooltip on hover. It and all wrappers should take the feature as the first param and the options func as the second.
|
||||
|
||||
Similar to a feature, the wrapper should make a lazy proxy with all its properties. But, you can't add this lazy object to the feature directly, because that would force its evaluation. Instead, you should use [runAfterEvaluation](/api/util/proxies/functions/runAfterEvaluation) and inside its callback add the wrapper object to the feature and make sure the wrapper object gets evaluated by referencing a property within it. If it is a wrapper around a vue component, you can also add the wrapper element to the feature's `wrappers` array inside the callback.
|
|
@ -10,7 +10,7 @@ This file has 3 things it must export, but beyond that can export anything the c
|
|||
|
||||
### getInitialLayers
|
||||
|
||||
- Type: `(player: Partial<PlayerData>) => GenericLayer[]`
|
||||
- Type: `(player: Partial<PlayerData>) => Layer[]`
|
||||
|
||||
A function that is given a player save data object currently being loaded, and returns a list of layers that should be active for that player. If a project does not have dynamic layers, this should always return a list of all layers.
|
||||
|
||||
|
|
|
@ -150,3 +150,10 @@ Whether or not to allow the player to pause the game. Turning this off disables
|
|||
- Default: `base64`
|
||||
|
||||
The encoding to use when exporting to the clipboard. Plain-text is fast to generate but is easiest for the player to manipulate and cheat with. Base 64 is slightly slower and the string will be longer but will offer a small barrier to people trying to cheat. LZ-String is the slowest method, but produces the smallest strings and still offers a small barrier to those trying to cheat. Some sharing platforms like pastebin may automatically delete base64 encoded text, and some sites might not support all the characters used in lz-string exports.
|
||||
|
||||
### disableHealthWarning
|
||||
|
||||
- Type: `boolean`
|
||||
- Default: `false`
|
||||
|
||||
Whether or not to disable the health warning that appears to the player after excessive playtime (activity during 6 of the last 8 hours). If left enabled, the player will still be able to individually turn off the health warning in settings or by clicking "Do not show again" in the warning itself.
|
||||
|
|
|
@ -25,5 +25,3 @@ Toggles whether to display tab buttons in a tab list, similar to how a browser d
|
|||
- Type: `boolean`
|
||||
|
||||
If true, elements in a row or column will have their margins removed and border radiuses set to 0 between elements. This will cause the elements to appear as segments in a single object.
|
||||
|
||||
Currently, this can only merge in a single dimension. Rows of columns or columns of rows will not merge into a single rectangular object.
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
# Example Projects
|
||||
|
||||
## Planar Pioneers <Badge type="tip" text="Profectus 0.6" />
|
||||
|
||||
[View Source](https://github.com/thepaperpilot/planar-pioneers/) | [View Project](https://galaxy.click/play/64)
|
||||
|
||||
An incremental game with procedurally generated content.
|
||||
|
||||
## this crazy idea <Badge type="tip" text="Profectus 0.6" />
|
||||
|
||||
[View Source](https://gitlab.com/yhvr/to-be-named) | [View Project](https://galaxy.click/play/94)
|
||||
|
||||
A "hopeless startup simulator", made for the [Profectus Creation Jam](https://itch.io/jam/profectus-creation-jam)
|
||||
|
||||
## Kronos <Badge type="tip" text="Profectus 0.6" />
|
||||
|
||||
[View Source](https://github.com/thepaperpilot/kronos/)
|
||||
|
||||
This is a project that's still under development but is a good resource for things like implementing custom features.
|
||||
|
||||
## TMT-Demo <Badge type="tip" text="Profectus 0.6" />
|
||||
## Demo <Badge type="tip" text="Profectus 0.7" />
|
||||
|
||||
[View Source](https://code.incremental.social/profectus/TMT-Demo) | [View Project](https://profectus.pages.incremental.social//TMT-Demo/)
|
||||
|
||||
A project loosely based off the Demo project for TMT. Uses most of the different features of Profectus, but doesn't have any real gameplay.
|
||||
|
||||
## Advent Incremental <Badge type="warning" text="Profectus 0.5" />
|
||||
## Planar Pioneers <Badge type="warning" text="Profectus 0.6" />
|
||||
|
||||
[View Source](https://github.com/thepaperpilot/planar-pioneers/) | [View Project](https://galaxy.click/play/64)
|
||||
|
||||
An incremental game with procedurally generated content.
|
||||
|
||||
## this crazy idea <Badge type="warning" text="Profectus 0.6" />
|
||||
|
||||
[View Source](https://gitlab.com/yhvr/to-be-named) | [View Project](https://galaxy.click/play/94)
|
||||
|
||||
A "hopeless startup simulator", made for the [Profectus Creation Jam](https://itch.io/jam/profectus-creation-jam)
|
||||
|
||||
## Kronos <Badge type="warning" text="Profectus 0.6" />
|
||||
|
||||
[View Source](https://github.com/thepaperpilot/kronos/)
|
||||
|
||||
This is a project that's still under development but is a good resource for things like implementing custom features.
|
||||
|
||||
## Advent Incremental <Badge type="danger" text="Profectus 0.5" />
|
||||
|
||||
[View Source](https://github.com/thepaperpilot/advent-Incremental/) | [View Project](https://www.thepaperpilot.org/advent/)
|
||||
|
||||
An incremental game with 25 different layers of content. A good example of what a large project looks like. There's also a partial port to 0.6 available [here](https://github.com/thepaperpilot/advent-Incremental/tree/next).
|
||||
|
||||
## Primordia <Badge type="warning" text="Profectus 0.5" />
|
||||
## Primordia <Badge type="danger" text="Profectus 0.5" />
|
||||
|
||||
[View Source](https://github.com/Jacorb90/Primordial-Tree) | [View Project](https://jacorb90.me/Primordial-Tree/)
|
||||
|
||||
|
|
|
@ -8,15 +8,15 @@ This page is a guide on where to start with creating your very first layer. It i
|
|||
The template comes with a layer in `projEntry.tsx` and another in `prestige.tsx`. You can use those as a base instead of creating a new one from scratch.
|
||||
:::
|
||||
|
||||
To add a new layer, first create it via the [createLayer](../../api/modules/game/layers#createlayer) function. You typically create a single layer per file, the first being in `projEntry`. After it is created, you'll need to add it to the returned array in [getInitialLayers](../../api/modules/data/projEntry#getinitiallayers).
|
||||
To add a new layer, first create it via the [createLayer](/api/game/layers/functions/createLayer) function. You typically create a single layer per file, the first being in `projEntry`. After it is created, you'll need to add it to the returned array in [getInitialLayers](/api/data/projEntry/functions/getInitialLayers).
|
||||
|
||||
The `createLayer` function will need a unique ID for your layer and a function that constructs the layer. At a minimum, it needs a `display` property so Profectus knows what to render. It will look something like this:
|
||||
|
||||
```ts
|
||||
const id = "p";
|
||||
const layer = createLayer(id, function (this: BaseLayer) {
|
||||
const layer = createLayer(id, layer => {
|
||||
return {
|
||||
display: jsx(() => <>My layer</>)
|
||||
display: () => <>My layer</>
|
||||
};
|
||||
});
|
||||
```
|
||||
|
@ -27,26 +27,26 @@ Game elements in Profectus are called "features". You'll create them inside the
|
|||
|
||||
In the file tree, there's a folder called `src/features`, which contains all the different features that are included in Profectus (and any others you've created or downloaded from others!). You can browse the folder to see all the features and learn what each one does. Some features also have dedicated guide pages on how to use them.
|
||||
|
||||
Let's add one of these features to our layer: a Resource. As with most features, there's a [createResource](../../api/modules/features/resource#createresource) constructor for creating this feature. These constructors typically take a function that returns an object with all the options for that feature. However, resources are simple features, so they just take the options as parameters. Creating a resource will look like this:
|
||||
Let's add one of these features to our layer: a Resource. As with most features, there's a [createResource](/api/features/resources/resource/functions/createResource) constructor for creating this feature. These constructors typically take a function that returns an object with all the options for that feature. However, resources are simple features, so they just take the options as parameters. Creating a resource will look like this:
|
||||
|
||||
```ts
|
||||
const points = createResource<DecimalSource>(0, "prestige points");
|
||||
```
|
||||
|
||||
In your IDE you'll be able to see the documentation for each parameter - in this case, the first one is the default value for this resource, and the second is the display name for the resource. Now we can make sure to add the points to our layer's object and display it using the [MainDisplay](../../api/modules/features/resource#maindisplay-component) Vue component. All in all, our layer should look like this now:
|
||||
In your IDE you'll be able to see the documentation for each parameter - in this case, the first one is the default value for this resource, and the second is the display name for the resource. Now we can make sure to add the points to our layer's object and display it using the [MainDisplay](/api/features/resources/components/MainDisplay) Vue component. All in all, our layer should look like this now:
|
||||
|
||||
```ts
|
||||
const id = "p";
|
||||
const layer = createLayer(id, function (this: BaseLayer) {
|
||||
const layer = createLayer(id, layer => {
|
||||
const points = createResource<DecimalSource>(0, "prestige points");
|
||||
|
||||
return {
|
||||
points,
|
||||
display: jsx(() => (
|
||||
display: () => (
|
||||
<>
|
||||
<MainDisplay resource={points} />
|
||||
</>
|
||||
))
|
||||
)
|
||||
};
|
||||
});
|
||||
```
|
||||
|
@ -56,16 +56,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
|
|||
Some things happen every tick, such as passive resource generation. You can hook into the update loop using an event bus. There's a global one and one for each layer. For example, within the layer function, you can add this code to our example to increase our points at a rate of 1 per second:
|
||||
|
||||
```ts
|
||||
this.on("update", diff => {
|
||||
layer.on("update", diff => {
|
||||
points.value = Decimal.add(points.value, diff);
|
||||
});
|
||||
```
|
||||
|
||||
Note that within the `createLayer`'s function, `this` refers to the layer you're actively creating, and the `diff` parameter represents the seconds that have passed since the last update - which will typically be around 0.05 unless throttling is disabled in the settings. If we wanted to generate an amount other than 1/s, we could multiply diff by the per-second rate.
|
||||
Note that the `createLayer`'s function receives a parameter for the base layer, which you may have to add. The `diff` parameter insidde the event callback represents the seconds that have passed since the last update - which will typically be around 0.05 unless throttling is disabled in the settings. If we wanted to generate an amount other than 1/s, we could multiply diff by the per-second rate.
|
||||
|
||||
## Adding an upgrade
|
||||
|
||||
Let's add a more complex feature to the layer now - an upgrade. Upgrades represent one-time purchases. This time the [createUpgrade](../../api/modules/features/upgrade#createupgrade) function requires an options function. We can create a lambda that returns the options object. We'll need to give it a cost requirement and display. Afterwards, it should look like this:
|
||||
Let's add a more complex feature to the layer now - an upgrade. Upgrades represent one-time purchases. This time the [createUpgrade](/api/features/clickables/upgrade/functions/createUpgrade) function requires an options function. We can create a lambda that returns the options object. We'll need to give it a cost requirement and display. Afterwards, it should look like this:
|
||||
|
||||
```ts
|
||||
const myUpgrade = createUpgrade(() => ({
|
||||
|
@ -79,18 +79,18 @@ const myUpgrade = createUpgrade(() => ({
|
|||
}));
|
||||
```
|
||||
|
||||
We'll add this upgrade to our returned object and our display. Upgrades are a renderable feature, which means we can use the [render](../../api/modules/util/vue#render) function to display them. The returned layer object will now look like this:
|
||||
We'll add this upgrade to our returned object and our display. Upgrades are a renderable feature, which means we can use the [render](/api/util/vue/functions/render) function to display them. The returned layer object will now look like this:
|
||||
|
||||
```ts
|
||||
return {
|
||||
points,
|
||||
myUpgrade,
|
||||
display: jsx(() => (
|
||||
display: () => (
|
||||
<>
|
||||
<MainDisplay resource={points} />
|
||||
{render(myUpgrade)}
|
||||
</>
|
||||
))
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -32,8 +32,6 @@ Upon action completion, you will now have a `pages` branch which is automaticall
|
|||
If you don't have a preferred IDE, Profectus is developed in [Visual Studio Code](https://code.visualstudio.com) and is known to work well with it.
|
||||
|
||||
Recommendations:
|
||||
- Use [Take Over Mode](https://vuejs.org/guide/typescript/overview.html#volar-takeover-mode) for proper type analysis
|
||||
- Turn off `.value` autocomplete by running the `Preferences: Open Settings` command and setting `volar.autoCompleteRefs` to `false`
|
||||
- Disable emmet expansions by setting `emmet.showExpandedAbbreviation` to `"never"`, also in the preferences
|
||||
- Install the [Vitest VS Code extension](https://marketplace.visualstudio.com/items?itemName=ZixuanChen.vitest-explorer&ssr=false#qna) for running and debugging unit tests (if working on the engine itself)
|
||||
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
# Coercable Components
|
||||
|
||||
Most times a feature has some sort of dynamic display, it'll allow you to pass a "Coercable Component", or rather, something that can be coerced into a Vue component. This page goes over the different types of values you can use
|
||||
|
||||
## Template Strings
|
||||
|
||||
If you provide a string, it will be wrapped in a component using it as the template. This is the simplest method, although not suitable for complex displays, and realistically cannot use Vue components as none are registered globally (by default). Recommended for static or simple dynamic displays, such as displays on features.
|
||||
|
||||
Template strings need to be wrapped in some HTML element. By default, they'll be wrapped in a `<span>` element, although certain features may wrap things in div or header elements instead, as appropriate.
|
||||
|
||||
## Render Functions (JSX)
|
||||
|
||||
You can provide a render function and it will be wrapped in a component as well. The intended use for this is to write JSX inside a function, which will get automatically converted into a render function. You can read more about that process on the Vue docs on [Render Functions & JSX](https://vuejs.org/guide/extras/render-function.html#render-functions-jsx). Note that JSX must be returned in a function - it does not work "standalone". The CoercableComponent type will enforce this for you. Also of note is that you can use `<>` and `</>` as wrappers to render multiple elements without a containing element, however keep in mind an empty JSX element such as `jsx(() => <></>)` is invalid and will fail to render.
|
||||
|
||||
JSX can use imported components, making this suited for writing the display properties on things like Tabs or Layers. There are also built-in functions to `render` features (either as their own or in a layout via `renderRow` and `renderCol`), so you don't need to import the Vue component for every feature you plan on using.
|
||||
|
||||
Typically a feature will accept a `Computable<CoercableComponent>`, which means functions would (normally) be wrapped in a computed (see [Computable](./reactivity#computable) for more details). This would break render functions, so when passing a render function as a CoercableComponent it must be specially marked that it shouldn't be cached. You can use the built-in `jsx` function to mark a function for you.
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
{
|
||||
display: jsx(() => (
|
||||
<>
|
||||
<MainDisplay resource={points} color={color} />
|
||||
{render(resetButton)}
|
||||
{renderRow(upgrade1, upgrade2, upgrade3)}
|
||||
</>
|
||||
)),
|
||||
}
|
||||
```
|
||||
|
||||
### Slots and Models
|
||||
|
||||
Modals and other features that utilize slots are a bit trickier in JSX, as each slot must _also_ be JSX. Here's an example utility for creating modals that correctly uses slots:
|
||||
|
||||
```tsx
|
||||
function createModal(title: string, body: JSXFunction, otherData = {}) {
|
||||
const showModal = persistent<boolean>(false);
|
||||
const modal = jsx(() => (
|
||||
<Modal
|
||||
modelValue={showModal.value}
|
||||
onUpdate:modelValue={(value: boolean) => (showModal.value = value)}
|
||||
v-slots={{
|
||||
header: () => <h2>{title}</h2>,
|
||||
body
|
||||
}}
|
||||
/>
|
||||
));
|
||||
return { modal, showModal, ...otherData };
|
||||
}
|
||||
```
|
||||
|
||||
That example also shows how to use models in JSX, which are a concept in vue for allowing a component to read and write a value. It requires specifying both the model value as well as a function to update it's value.
|
||||
|
||||
## Components
|
||||
|
||||
This one might be the most obvious, but you can also just give it a Vue component to display outright. Keep in mind it will not be passed any props, so it should not depend on any. You can read more about creating Vue components on [Components Basics](https://vuejs.org/guide/essentials/component-basics.html).
|
|
@ -10,8 +10,10 @@ const addGainUpgrade = createUpgrade(() => ({
|
|||
title: "Generator of Genericness",
|
||||
description: "Gain 1 point every second"
|
||||
},
|
||||
cost: 1,
|
||||
resource: points
|
||||
requirements: createCostRequirement(() => ({
|
||||
resource: noPersist(points),
|
||||
cost: 1
|
||||
}))
|
||||
}));
|
||||
```
|
||||
|
||||
|
@ -28,6 +30,18 @@ const upgrades = { addGainUpgrade, gainMultUpgrade, upgMultUpgrade };
|
|||
const numUpgrades = computed(() => Object.values(upgrades).length);
|
||||
```
|
||||
|
||||
## Displays
|
||||
|
||||
Most times a feature has some sort of dynamic display, it'll allow you to pass a string or `JSX.Element`, the latter within a function or the former either dynamically or statically.
|
||||
|
||||
### Template Strings
|
||||
|
||||
Providing a string is the simplest method, although not suitable for complex displays and cannot use Vue components. Recommended for static or simple dynamic displays, such as titles or descriptions on features. These strings will be rendered as text and not be parsed as HTML. Some features may wrap the text in header elements or others as appropriate, but this can be overidden by using JSX instead.
|
||||
|
||||
### Render Functions (JSX)
|
||||
|
||||
You can provide a render function to have more control over what is displayed. Here you can include vue components and even other features, and is how the layer's display itself should be defined. You can use `<>` and `</>` as wrappers to render multiple elements without a containing element. You can read other details about how JSX works in the Vue docs on [Render Functions & JSX](https://vuejs.org/guide/extras/render-function.html#render-functions-jsx), particularly how advanced featuers like slots and models work.
|
||||
|
||||
## Tree Shaking
|
||||
|
||||
Since Profectus takes advantage of [tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking), any type of feature that is not used will not be included in the output of the project. That means users have less code to download, a slight performance boost, and you don't need to worry about feature type-specific settings appearing (such as whether to show maxed challenges).
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Profectus utilizes formulas for various features, such as increasing requirements for repeatables and challenges or determining resource gains in conversions. These formulas often need to be inverted or integrated to enable features like buying multiple levels of a repeatable at once or determining when a conversion will increase resource gains. The Formula class can handle these operations, supporting every function Decimal does, while tracking the operations internally.
|
||||
|
||||
For example, a cost function like `Decimal.pow(this.amount, 1.05).times(100)` can be represented using a Formula: `Formula.variable(this.amount).pow(1.05).times(100)`.
|
||||
For example, a cost function like `Decimal.pow(amount, 1.05).times(100)` can be represented using a Formula: `Formula.variable(amount).pow(1.05).times(100)`.
|
||||
|
||||
```ts
|
||||
const myRepeatable = createRepeatable(() => ({
|
||||
|
@ -34,7 +34,7 @@ Certain operations may not support inverting or integrating. Functions such as r
|
|||
|
||||
### Spending Resources
|
||||
|
||||
When working with formulas, the `spendResources` property determines whether the formula needs to be invertible or integrable. The property is used in two utilities: [calculateMaxAffordable](/api/modules/game/formulas/formulas#calculatemaxaffordable) and [calculateCost](/api/modules/game/formulas/formulas#calculatecost). These utilities are ultimately employed wherever formulas are used, such as in cost requirements or conversions.
|
||||
When working with formulas, the `spendResources` property determines whether the formula needs to be invertible or integrable. The property is used in two utilities: [calculateMaxAffordable](/api/game/formulas/formulas/functions/calculateMaxAffordable) and [calculateCost](/api/game/formulas/formulas/functions/calculateCost). These utilities are ultimately employed wherever formulas are used, such as in cost requirements or conversions.
|
||||
|
||||
Spending resources refers to whether max affordability and cost calculations should account for resources spent. If spending resources is set to true, the formula will be integrated and inverted, albeit with certain limitations. For example, exponential modifiers and soft caps can make a formula non-integrable.
|
||||
|
||||
|
@ -44,7 +44,7 @@ Integration in this context is an estimation, as it calculates the area under a
|
|||
|
||||
### Modifiers
|
||||
|
||||
When applying changes to a formula using [modifiers](/api/modules/game/modifiers), use the `modifierToFormula` utility to apply the modifier while preserving invertibility if the modifier is invertible.
|
||||
When applying changes to a formula using [modifiers](/api/game/modifiers/), use the `modifierToFormula` utility to apply the modifier while preserving invertibility if the modifier is invertible.
|
||||
|
||||
### Custom Formulas
|
||||
|
||||
|
|
|
@ -10,4 +10,6 @@ It's important for saving and loading these properties for these refs to be in a
|
|||
|
||||
Additionally, this structure should typically remain consistent between project versions. If a value is in a new location, it will not load the value from localStorage correctly. This is exacerbated if two values swap places, such as when an array is re-ordered. In the event a creator changes this structure anyways, the [fixOldSave](../creating-your-project/project-entry.md#fixoldsave) function can be used to migrate the old player save data to the new structure expected by the current version of the project.
|
||||
|
||||
As of Profectus 0.6, save data will now report warnings whenever there is redundancy - two locations for the same persistent data, which creates larger saves that can cause issues when loading after updates. To fix redundancies, wrap all but one location for the data in [noPersist](../../api/modules/game/persistence#nopersist).
|
||||
As of Profectus 0.6, save data will now report warnings whenever there is redundancy - two locations for the same persistent data, which creates larger saves that can cause issues when loading after updates. To fix redundancies, wrap all but one location for the data in [noPersist](/api/game/persistence/functions/noPersist).
|
||||
|
||||
One place to look out for specifically is tree nodes, which typically have a persisten `pinned` value and can appear in both the nodes array as well as the links array.
|
||||
|
|
|
@ -6,8 +6,8 @@ With a proper IDE, such as [Visual Studio Code](../getting-started/setup#visual-
|
|||
|
||||
Vue's reactivity is probably the "quirkiest" part of Profectus, and not even the documentation makes all of those quirks clear. It is recommend to read [this thread](https://github.com/vuejs/docs/issues/849) of common misconceptions around Vue reactivity.
|
||||
|
||||
## Computable
|
||||
## Optionally computable values
|
||||
|
||||
Most properties on features will accept `Computable` values. Computable values can either be a raw value, a ref to the value, or a function that returns the value. In the lattermost case it will be wrapped in `computed`, turning it into a ref. The feature type will handle it being a ref or a raw value by using `unref` when accessing those values. With type hints, your IDE should correctly identify these values as refs or raw values so you can treat them as the types they actually are.
|
||||
Most properties on features will accept `MaybeRefOrGetter` values. These properties can receive either a raw value, a ref to the value, or a function that returns the value. In the lattermost case it will be wrapped in `computed`, turning it into a ref. The feature type will handle it being a ref or a raw value by using `unref` when accessing those values. With type hints, your IDE should correctly identify these values as refs or raw values so you can treat them as the types they actually are.
|
||||
|
||||
Because functions are automatically wrapped in `computed` for many properties, it might be expected to happen to custom properties you add to a feature that isn't defined by the feature type. These functions will _not_ be wrapped, and if you want it cached you should wrap it in a `computed` yourself. This does, however, allow you to include custom methods on a feature without worry.
|
||||
|
|
|
@ -4,30 +4,29 @@ The requirements system in Profectus is designed to handle various conditions th
|
|||
|
||||
## Creating Requirements
|
||||
|
||||
To create a requirement, you can use one of the provided utility functions like [createCostRequirement](/api/modules/game/requirements#createcostrequirement), [createVisibilityRequirement](/api/modules/game/requirements#createvisibilityrequirement), or [createBooleanRequirement](/api/modules/game/requirements#createbooleanrequirement). These functions return a `Requirement` object with specific properties that define the requirement conditions.
|
||||
To create a requirement, you can use one of the provided utility functions like [createCostRequirement](/api/game/requirements/functions/createCostRequirement), [createVisibilityRequirement](/api/game/requirements/functions/createVisibilityRequirement), or [createBooleanRequirement](/api/game/requirements/functions/createBooleanRequirement). These functions return a `Requirement` object with specific properties that define the requirement conditions.
|
||||
|
||||
Cost requirements are probably the most common requirement you'll be using. For something with multiple levels, like repeatables, you'll typically want to use a formula for the cost instead of a function, and the input to the formula will be the repeatable's `amount` property. Typically that means the code will look like this:
|
||||
|
||||
```ts
|
||||
createRepeatable(repeatable => ({
|
||||
requirements: createCostRequirement(() => ({
|
||||
resource: points,
|
||||
cost: Formula.variable(repeatable.amount).add(1).times(100)
|
||||
}))
|
||||
const repeatable = createRepeatable(() => ({
|
||||
requirements: createCostRequirement(() => ({
|
||||
resource: noPersist(points),
|
||||
cost: Formula.variable(repeatable.amount).add(1).times(100)
|
||||
}))
|
||||
}));
|
||||
```
|
||||
Important to note here is the parameter added to the `createRepeatable`'s options function. That is a reference to the repeatable being created, so you can access it's `amount` property in the formula.
|
||||
|
||||
## Using Requirements
|
||||
|
||||
Several features will have a `requirements` property that takes one or more requirements that must be fulfilled for a certain action to be performed with that feature.
|
||||
|
||||
When implementing requirements, you can use the [displayRequirements](/api/modules/game/requirements#displayrequirements) utility. This utility is designed to give a human-readable string generated by the requirement(s) given, making it easier for players to understand the conditions needed to progress in the game. Typically features that support requirements will already use this utility internally.
|
||||
When implementing requirements, you can use the [displayRequirements](/api/game/requirements/functions/displayRequirements) utility. This utility is designed to give a human-readable string generated by the requirement(s) given, making it easier for players to understand the conditions needed to progress in the game. Typically features that support requirements will already use this utility internally.
|
||||
|
||||
You may need to "pay" requirements upon meeting their conditions and performing the associated action. This action typically involves spending resources or making other adjustments to the game state. The [payRequirements](/api/modules/game/requirements#payrequirements) function simplifies this process by handling the payment for one or more requirements, considering the number of levels to pay for. Additionally, custom pay functions like [payByDivision](/api/modules/game/requirements#paybydivision) and [payByReset](/api/modules/game/requirements#paybyreset) can be passed into `createCostRequirement` for more specialized cases.
|
||||
You may need to "pay" requirements upon meeting their conditions and performing the associated action. This action typically involves spending resources or making other adjustments to the game state. The [payRequirements](/api/game/requirements/functions/payRequirements) function simplifies this process by handling the payment for one or more requirements, considering the number of levels to pay for. Additionally, custom pay functions like [payByDivision](/api/game/requirements/functions/payByDivision) and [payByReset](/api/game/requirements/functions/payByReset) can be passed into `createCostRequirement` for more specialized cases.
|
||||
|
||||
## Multi-Level Requirements
|
||||
|
||||
Requirements can have multiple "levels", which are typically used for things like multi-level challenges with scaling requirements. When checking if requirements are met, the [requirementsMet](/api/modules/game/requirements#requirementsmet) function can be used. It accepts a single requirement or an array of requirements.
|
||||
Requirements can have multiple "levels", which are typically used for things like multi-level challenges with scaling requirements. When checking if requirements are met, the [requirementsMet](/api/game/requirements/functions/requirementsMet) function can be used. It accepts a single requirement or an array of requirements.
|
||||
|
||||
Requirements that are just on/off, such as boolean or visibility requirements, will count as infinite levels when in the same array. This allows you to combine different types of requirements in the same array and use the [maxRequirementsMet](/api/modules/game/requirements#maxrequirementsmet) function to calculate the maximum number of levels that could be acquired with the current requirement states.
|
||||
Requirements that are just on/off, such as boolean or visibility requirements, will count as infinite levels when in the same array. This allows you to combine different types of requirements in the same array and use the [maxRequirementsMet](/api/game/requirements/functions/maxRequirementsMet) function to calculate the maximum number of levels that could be acquired with the current requirement states.
|
||||
|
|
|
@ -3,7 +3,7 @@ title: Introduction
|
|||
---
|
||||
# Introduction
|
||||
|
||||
Profectus is a web-based game engine. You can write your content using many built in features, write your own features, and build up complex gameplay quickly and easily.
|
||||
Profectus is a template for web-based games and projects. You can write your content using many built in features, write your own features, and build up complex gameplay quickly and easily.
|
||||
|
||||
The purpose of creating profectus was to create an easy to use engine that does not create a ceiling for a programmer's personal growth. This engine will grow in complexity with you, empowering you to create increasingly complex designs and mechanics.
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ return {
|
|||
}
|
||||
```
|
||||
|
||||
This example stores the same persistent data in two locations - `flowers.flowers` and `flowers.job.resource`. We can mark the latter usage as a reference by wrapping it in the [noPersist](../../api/modules/game/persistence#nopersist) utility, so it'd look like `resource: noPersist(flowers)`. Otherwise, you will encounter an error in the console when the layer is loaded:
|
||||
This example stores the same persistent data in two locations - `flowers.flowers` and `flowers.job.resource`. We can mark the latter usage as a reference by wrapping it in the [noPersist](/api/game/persistence/functions/noPersist) utility, so it'd look like `resource: noPersist(flowers)`. Otherwise, you will encounter an error in the console when the layer is loaded:
|
||||
|
||||
![Persistence Error](./persistence-error.png)
|
||||
|
||||
|
@ -159,6 +159,4 @@ Be aware that using the computed ref directly instead of a function can cause ci
|
|||
|
||||
### Custom Components
|
||||
|
||||
If you created any custom features with their own Vue components, you'll need to update them to support booleans for visibility values. This means replacing **ALL** equality checks for specific visibilities with calls to [isVisible](../../api/modules/features/feature#isvisible) and [isHidden](../../api/modules/features/feature#ishidden).
|
||||
|
||||
While updating your component, you may need to cast the component to [GenericComponent](../../api/modules/features/feature#genericcomponent).
|
||||
If you created any custom features with their own Vue components, you'll need to update them to support booleans for visibility values. This means replacing **ALL** equality checks for specific visibilities with calls to [isVisible](/api/features/feature/functions/isVisible) and [isHidden](/api/features/feature/functions/isHidden).
|
||||
|
|
120
docs/guide/migrations/0-7.md
Normal file
120
docs/guide/migrations/0-7.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
# Migrating to Profectus 0.7
|
||||
|
||||
This update involved the feature rewrite and board rewrite, the largest refactors in Profectus' history at time of release. Here's how you can handle these changes.
|
||||
|
||||
## Boards
|
||||
|
||||
If you have an existing board, there's no real way to migrate to the new board system. You'll likely want to either re-create it within the new system (see how to [here](../advanced-concepts/boards)) or use the legacy board implementation available [here](https://forums.moddingtree.com/t/profectus-board-feature/1656).
|
||||
|
||||
## Grids
|
||||
|
||||
Grids have been removed, and you'll need to either use Row and Column directly with whatever component you like, or use the legacy grid implementation available [here](https://forums.moddingtree.com/t/profectus-grid-feature/1651).
|
||||
|
||||
## Generic Feature Types
|
||||
|
||||
Feature types no longer take the options as a type parameter, and are therefore generic by default. Any uses of the previous types (the generic type or passing a type parameter) will need to be updated. The typing of a feature should include any custom properties you add to the options object.
|
||||
|
||||
## Displays
|
||||
|
||||
Displays are a bit different. There is no `jsx` function nor `JSXFunction` types anymore. Instead, you'll just use a function directly, or just a string if its purely static. Any feature that would previously except a function or ref returning an object of individual pieces to display (e.g. description, title, etc.) will now take the object directly, and allow each individual piece to be a function or ref.
|
||||
|
||||
## Tooltips and Marks
|
||||
|
||||
Tooltips and marks now take options funcs like other features. Additionally, they're now considered "wrappers" so the import statement will need adjusting.
|
||||
|
||||
## `this` in options funcs
|
||||
|
||||
As a general rule, you should not use `this` inside of the functions that return the options object for a feature. In some cases it'll still work, but often will not. The typing of the features should allow you to reference the feature by name without causing cyclical dependencies now, though. So, for example, this code is perfectly fine:
|
||||
|
||||
```ts
|
||||
const myMilestone = createAchievement(() => ({
|
||||
...,
|
||||
style() {
|
||||
if (myMilestone.earned.value !== false) {
|
||||
return { "--layer-color": "#1111DD" };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}));
|
||||
```
|
||||
|
||||
## Decorators
|
||||
|
||||
The decorators system is no longer necessary with the rewrite, so features do not support them anymore. The bonus amount/completions decorators have been converted to mixins, so you can still use them.
|
||||
|
||||
## Styling
|
||||
|
||||
`style` properties should now always be in the form of an object, not a string. This allows them to more easily override individual properties. Additionally, vue features are structured a little bit differently in the DOM, so you may need to update any custom CSS you wrote.
|
||||
|
||||
## Custom features
|
||||
|
||||
The feature rewrite, obviously, most significantly impacts any custom features you've written. The general process for updating a feature went like this:
|
||||
|
||||
### Update types
|
||||
|
||||
The options object should now extend `VueFeatureOptions`, and every Computable should be replaced with MaybeRefOrGetter. You'll no longer need the `visibility`, `style` and `classes` properties. If you had a `mark` property that can also be removed. If you have a custom display object, make the properties within it `MaybeGetter<Renderable>`, and don't wrap the object itself in anything (neither `Computable` nor `MaybeRefOrGetter`). Otherwise, you can just make it take a `MaybeGetter<Renderable>` directly.
|
||||
|
||||
The `Base` variant of your feature is going to become the new interface for the feature itself. You'll make it extend `VueFeature` and remove the `id`, `[Component]`, and `[GatherProps]` properties. Since we're not relying on any utility functions here, you'll need to add the `MaybeRef` version of every `MaybeRefOrGetter` property in the options object, and copy over any other properties that just get passed through. You won't handle default values for the properties since this is a generic interface, but you can mark those properties as required since they'll always be present. You can then delete the previous type for the feature itself and the `Generic` variant of it.
|
||||
|
||||
### Update constructor
|
||||
|
||||
First, make the options func simply typed as `() => T`, and remove the return type annotation. You'll also remove the `feature` parameter from the function passed to `createLazyProxy`. Instead of creating the feature object immediately, we'll just destructure the options, and create the feature object later. For example, here's the first section of `createInfobox`'s proxy function:
|
||||
|
||||
```ts
|
||||
const options = optionsFunc();
|
||||
const { color, titleStyle, bodyStyle, title, display, ...props } = options;
|
||||
```
|
||||
|
||||
We can then construct the feature object, passing in `type`, adding the extra props and vue feature properties, and handling processing getters and default values. Here's the end of the `createInfobox` proxy function:
|
||||
|
||||
```ts
|
||||
const infobox = {
|
||||
type: InfoboxType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof InfoboxOptions>),
|
||||
...vueFeatureMixin("infobox", options, () => (
|
||||
<Infobox
|
||||
color={infobox.color}
|
||||
titleStyle={infobox.titleStyle}
|
||||
bodyStyle={infobox.bodyStyle}
|
||||
collapsed={infobox.collapsed}
|
||||
title={infobox.title}
|
||||
display={infobox.display}
|
||||
/>
|
||||
)),
|
||||
collapsed,
|
||||
color: processGetter(color) ?? "--layer-color",
|
||||
titleStyle: processGetter(titleStyle),
|
||||
bodyStyle: processGetter(bodyStyle),
|
||||
title,
|
||||
display
|
||||
} satisfies Infobox;
|
||||
|
||||
return infobox;
|
||||
```
|
||||
|
||||
For more complex use cases, I'd check out the other constructors of existing features. You can also ask the [discord](https://discord.gg/yJ4fjnjU54) if you're having trouble.
|
||||
|
||||
If you'd added support for the previous decorators system, you'll remove all of that code from the feature as its no longer necessary with the feature rewrite.
|
||||
|
||||
### Update vue component
|
||||
|
||||
All the vue components in Profectus now use `script setup` and the composition API, and I recommend you use it as well. The props should just link to the individual properties in the class for maintainability (but you can'y just do `defineProps<MyFeature>()` because of the limitations on the vue compiler). Here's an example for Infobox:
|
||||
|
||||
```ts
|
||||
const props = defineProps<{
|
||||
color: Infobox["color"];
|
||||
titleStyle: Infobox["titleStyle"];
|
||||
bodyStyle: Infobox["bodyStyle"];
|
||||
collapsed: Infobox["collapsed"];
|
||||
display: Infobox["display"];
|
||||
title: Infobox["title"];
|
||||
}>();
|
||||
```
|
||||
|
||||
For rendering a `Renderable`, it should just be defining a PascalCase variable that's just a function passing the renderable into `render`, like so:
|
||||
|
||||
`const Title = () => render(props.title);`
|
||||
|
||||
You can then just add `<Title />` in the template.
|
||||
|
||||
Finally, make sure to remove the parts that are now handle by the vue feature mixin. Specifically, remove the `Node` component, the `Mark` component if present, and the handling for `visibility`, `style`, and `classes` props.
|
|
@ -19,11 +19,10 @@ Next, create the particles feature and render it. You'll also want to track the
|
|||
|
||||
```ts
|
||||
const particles = createParticles(() => ({
|
||||
fullscreen: false,
|
||||
zIndex: -1,
|
||||
style: { zIndex: "-1" },
|
||||
boundingRect: ref<null | DOMRect>(null),
|
||||
onContainerResized(boundingRect) {
|
||||
this.boundingRect.value = boundingRect;
|
||||
particles.boundingRect.value = boundingRect;
|
||||
}
|
||||
}));
|
||||
```
|
||||
|
@ -70,11 +69,10 @@ If you're using hot reloading, you might need to reload the particle effect. Her
|
|||
|
||||
```ts
|
||||
const particles = createParticles(() => ({
|
||||
fullscreen: false,
|
||||
zIndex: -1,
|
||||
style: { zIndex: "-1" },
|
||||
boundingRect: ref<null | DOMRect>(null),
|
||||
onContainerResized(boundingRect) {
|
||||
this.boundingRect.value = boundingRect;
|
||||
particles.boundingRect.value = boundingRect;
|
||||
},
|
||||
onHotReload() {
|
||||
Object.values(elements).forEach(element => element.refreshParticleEffect());
|
||||
|
|
|
@ -57,4 +57,4 @@ If you have multiple tiers of resets, you may add an `onReset` function to each
|
|||
|
||||
## Branching resets
|
||||
|
||||
A common pattern for reset mechanics is to display these various layers in a tree and have the tree propagate the resets for you. There are a couple of utility functions to help you accomplish this: [createResetButton](../../api/modules/data/common#createresetbutton) and [createLayerTreeNode](../../api/modules/data/common#createlayertreenode). You'll typically place the tree itself in the `main` layer and create a tree node for each layer. Then you can call the tree's `reset` function with the layer that's resetting, and it will propagate the reset as appropriate.
|
||||
A common pattern for reset mechanics is to display these various layers in a tree and have the tree propagate the resets for you. There are a couple of utility functions to help you accomplish this: [createResetButton](/api/data/common/functions/createResetButton) and [createLayerTreeNode](/api/data/common/functions/createLayerTreeNode). You'll typically place the tree itself in the `main` layer and create a tree node for each layer. Then you can call the tree's `reset` function with the layer that's resetting, and it will propagate the reset as appropriate.
|
||||
|
|
|
@ -11,18 +11,16 @@ This recipe will involve modifying the `Save.vue` file within your project to in
|
|||
Let's start with creating the coerced component. For this recipe we're going to make a couple assumptions about what this display should be. We'll assume the text will be more complex than displaying a single value. That is, at different stages of the game progress will be indicated by different metrics. We'll also assume it will be a single line of descriptive text - no images or anything else that would justify making a new .vue component. Breaking these assumptions is left as an exercise for the reader. But for now, with those assumptions in mind, we'll write our component (in the `<script>` tag in `Save.vue`) similar to this example:
|
||||
|
||||
```ts
|
||||
const progressDisplay = computeComponent(
|
||||
computed(() => {
|
||||
if (someCondition) {
|
||||
return "Just started";
|
||||
}
|
||||
if (someOtherCondition) {
|
||||
return `Early game; ${formatWhole(someResourceValue)} resource name`;
|
||||
}
|
||||
...
|
||||
return "Unknown progress";
|
||||
})
|
||||
);
|
||||
const progressDisplay = computed(() => {
|
||||
if (someCondition) {
|
||||
return "Just started";
|
||||
}
|
||||
if (someOtherCondition) {
|
||||
return `Early game; ${formatWhole(someResourceValue)} resource name`;
|
||||
}
|
||||
...
|
||||
return "Unknown progress";
|
||||
});
|
||||
```
|
||||
|
||||
This code will create a component that will simply render the returned text, and update as required. However, there's one significant complication in writing the code for these conditions and resource values: you're working with a potentially incomplete save data object.
|
||||
|
@ -50,8 +48,10 @@ The `Save.vue` template contains, amongst other things, an element that displays
|
|||
</button>
|
||||
<span class="save-version">v{{ save.modVersion }}</span
|
||||
><br />
|
||||
<div v-if="currentTime">Last played {{ dateFormat.format(currentTime) }}</div>
|
||||
++ <div v-if="progressDisplay"><component :is="progressDisplay" /></div>
|
||||
<div v-if="currentTime" class="time">
|
||||
Last played {{ dateFormat.format(currentTime) }}
|
||||
</div>
|
||||
<div v-if="progressDisplay">{{ progressDisplay }}</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# Profectus API
|
||||
|
||||
This section of the docs is generated via the doc comments inside the Profectus source code. While it can be used as a reference, the comments themselves should show up in your IDE when relevant, which is probably a better way to get help. The [guide](/guide/) has longer-form explanations of key concepts and is more likely to be of use to new developers.
|
50
package.json
50
package.json
|
@ -7,7 +7,7 @@
|
|||
"serve": "vitepress serve docs",
|
||||
"dev": "vitepress dev docs",
|
||||
"build": "vitepress build docs",
|
||||
"generate": "tsc --project ./profectus-theme && vue-docgen -c docgen.config.js && cd profectus && vue-typedoc --logLevel Verbose --plugin ../typedoc-theme/index.js --theme profectus --plugin typedoc-plugin-markdown --plugin typedoc-plugin-mdn-links --skipErrorChecking && cd .. && rm docs/api/modules/main.md && node postProcess.js"
|
||||
"generate": "vue-docgen -c docgen.config.js && cd profectus && typedoc --logLevel Verbose --plugin typedoc-vitepress-theme --plugin typedoc-plugin-markdown --plugin typedoc-plugin-mdn-links --skipErrorChecking && cd .. && node postProcess.js"
|
||||
},
|
||||
"repository": "git+https://code.incremental.social/profectus/profectus-docs.git",
|
||||
"author": "thepaperpilot",
|
||||
|
@ -17,34 +17,40 @@
|
|||
},
|
||||
"homepage": "https://code.incremental.social/profectus/profectus-docs#readme",
|
||||
"dependencies": {
|
||||
"vitepress": "^1.0.0-rc.22"
|
||||
"tm-themes": "^1.9.5",
|
||||
"typedoc": "^0.27.6",
|
||||
"typedoc-plugin-dt-links": "^1.1.4",
|
||||
"typedoc-plugin-markdown": "^4.3.3",
|
||||
"typedoc-plugin-mdn-links": "^4.0.6",
|
||||
"typedoc-vitepress-theme": "^1.1.1",
|
||||
"typescript": "~5.5.4",
|
||||
"vitepress": "^1.5.0",
|
||||
"vue-docgen-cli": "^4.79.0"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@pixi/app": "~6.3.2",
|
||||
"@pixi/constants": "~6.3.2",
|
||||
"@pixi/core": "~6.3.2",
|
||||
"@pixi/display": "~6.3.2",
|
||||
"@pixi/math": "~6.3.2",
|
||||
"@pixi/app": "^6.5.10",
|
||||
"@pixi/constants": "~6.5.10",
|
||||
"@pixi/core": "^6.5.10",
|
||||
"@pixi/display": "~6.5.10",
|
||||
"@pixi/math": "~6.5.10",
|
||||
"@pixi/particle-emitter": "^5.0.7",
|
||||
"@pixi/sprite": "~6.3.2",
|
||||
"@pixi/ticker": "~6.3.2",
|
||||
"@types/lz-string": "^1.3.34",
|
||||
"@pixi/sprite": "~6.5.10",
|
||||
"@pixi/ticker": "~6.5.10",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"nanoevents": "^6.0.2",
|
||||
"typedoc": "^0.23.28",
|
||||
"typedoc-plugin-markdown": "^3.14.0",
|
||||
"typedoc-plugin-mdn-links": "^3.0.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vitest": "^0.29.3",
|
||||
"vue-docgen-cli": "^4.66.0",
|
||||
"vue-next-select": "^2.10.2",
|
||||
"lz-string": "^1.5.0",
|
||||
"nanoevents": "^9.0.0",
|
||||
"unofficial-galaxy-sdk": "git+https://code.incremental.social/thepaperpilot/unofficial-galaxy-sdk.git#1.0.1",
|
||||
"vite": "^5.1.8",
|
||||
"vite-plugin-pwa": "^0.20.5",
|
||||
"vite-tsconfig-paths": "^4.3.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-next-select": "^2.10.5",
|
||||
"vue-panzoom": "https://github.com/thepaperpilot/vue-panzoom.git",
|
||||
"vue-textarea-autosize": "^1.1.1",
|
||||
"vue-toastification": "^2.0.0-rc.1",
|
||||
"vue-transition-expand": "^0.1.0",
|
||||
"vue-typedoc": "https://github.com/thepaperpilot/vue-typedoc.git",
|
||||
"vue-toastification": "^2.0.0-rc.5",
|
||||
"vuedraggable": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,15 +14,48 @@ function walk(dir, cb) {
|
|||
}));
|
||||
}
|
||||
|
||||
function sort(a, b) {
|
||||
if (a.items && !b.items) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.items && b.items) {
|
||||
return 1;
|
||||
}
|
||||
return a.text.localeCompare(b.text);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
// Replace overview file
|
||||
fs.copyFileSync("./overview.md", "./docs/api/overview.md");
|
||||
fs.unlinkSync("./docs/api/index.md");
|
||||
// Update sidebar file
|
||||
const sidebarText = fs.readFileSync("./docs/api/typedoc-sidebar.json")
|
||||
.toString()
|
||||
.replaceAll('{"text":"Functions","collapsed":true', '{"text":"Functions","collapsed":false')
|
||||
.replaceAll('{"text":"Interfaces","collapsed":true', '{"text":"Interfaces","collapsed":false')
|
||||
.replaceAll('{"text":"Type Aliases","collapsed":true', '{"text":"Type Aliases","collapsed":false')
|
||||
.replaceAll('{"text":"Variables","collapsed":true', '{"text":"Variables","collapsed":false')
|
||||
.replaceAll('{"text":"Classes","collapsed":true', '{"text":"Classes","collapsed":false');
|
||||
const sidebar = JSON.parse(sidebarText);
|
||||
fs.unlinkSync("./docs/api/typedoc-sidebar.json");
|
||||
|
||||
// Copy components over
|
||||
await walk("./components/components", (dir, file, resolve) => {
|
||||
const relPath = path.relative("./components/components", dir);
|
||||
let dest = path.resolve("./docs/api/components", relPath);
|
||||
await walk("./components", (dir, file, resolve) => {
|
||||
let relPath = path.relative("./components", dir);
|
||||
if (!relPath.includes("components") && relPath != "") {
|
||||
relPath += "/components";
|
||||
}
|
||||
let dest = path.resolve("./docs/api", relPath);
|
||||
let currentSidebar = sidebar;
|
||||
relPath.replaceAll("\\", "/").split("/").forEach(folder => {
|
||||
if (!folder) return;
|
||||
let nextSidebar = currentSidebar.find(f => f.text === folder);
|
||||
if (!nextSidebar) {
|
||||
nextSidebar = { text: folder, collapsed: true, items: [] };
|
||||
currentSidebar.push(nextSidebar);
|
||||
currentSidebar.sort(sort);
|
||||
}
|
||||
currentSidebar = nextSidebar.items;
|
||||
});
|
||||
currentSidebar.push({ text: path.basename(file).replace(".md", ""), link: `/..\\docs\\api/${relPath.replace("\\", "/")}/${path.basename(file)}` });
|
||||
currentSidebar.sort(sort);
|
||||
const filePath = path.resolve(dir, file);
|
||||
const stream = fs.createReadStream(filePath);
|
||||
let lineCount = 0;
|
||||
|
@ -42,51 +75,27 @@ function walk(dir, cb) {
|
|||
});
|
||||
});
|
||||
|
||||
// Write features' components to end of file
|
||||
await walk("./components/features", (dir, file, resolve) => {
|
||||
const relPath = path.relative("./components/features", dir);
|
||||
let dest = path.resolve("./docs/api/modules/features", relPath);
|
||||
|
||||
if (relPath == "infoboxes") {
|
||||
dest = dest.slice(0, -2);
|
||||
} else if (relPath === "tabs") {
|
||||
dest += file.includes("TabComponent") ? "\\tab" : "\\tabFamily";
|
||||
}
|
||||
|
||||
try {
|
||||
fs.accessSync(dest + ".md", fs.constants.R_OK | fs.constants.W_OK);
|
||||
} catch (err) {
|
||||
dest = dest.slice(0, -1);
|
||||
try {
|
||||
fs.accessSync(dest + ".md", fs.constants.R_OK | fs.constants.W_OK);
|
||||
} catch (err) {
|
||||
console.log("Couldn't find file at", dest + ".md");
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
dest = dest + ".md";
|
||||
let data = fs.readFileSync(dest).toString();
|
||||
const elementData = fs.readFileSync(path.resolve(dir, file));
|
||||
const fd = fs.openSync(dest, "w+");
|
||||
const componentsSection = data.indexOf("## Components");
|
||||
if (componentsSection == -1) {
|
||||
data += `\n## Components\n`;
|
||||
}
|
||||
fs.writeSync(fd, data);
|
||||
fs.writeSync(fd, "\n" + elementData + "\n");
|
||||
fs.closeSync(fd);
|
||||
resolve();
|
||||
});
|
||||
// Write updated sidebar
|
||||
fs.writeFileSync("./typedoc-sidebar.json", JSON.stringify(sidebar));
|
||||
|
||||
// Add frontmatter to every file
|
||||
const frontmatter = Buffer.from("---\neditLink: false\n---\n");
|
||||
await walk("./docs/api", function addFrontmatter(dir, file, resolve) {
|
||||
if (path.extname(file) !== ".md") return;
|
||||
const relPath = path.relative("./docs/api", dir);
|
||||
const filePath = path.resolve(dir, file);
|
||||
const data = fs.readFileSync(filePath).toString().replaceAll(/\[K in keyof T\]/g, "\\[K in keyof T]");
|
||||
const data = fs.readFileSync(filePath).toString()
|
||||
.replaceAll("`Ref`", "[`Ref`](https://vuejs.org/guide/typescript/composition-api.html#typing-ref)")
|
||||
.replaceAll("`MaybeRef`", "[`MaybeRef`](https://vuejs.org/api/utility-types.html#mayberef)")
|
||||
.replaceAll("`MaybeRefOrGetter`", "[`MaybeRefOrGetter`](https://vuejs.org/api/utility-types.html#maybereforgetter)")
|
||||
.replaceAll("`CSSProperties`", "[`CSSProperties`](https://vuejs.org/api/utility-types.html#cssproperties)")
|
||||
.replaceAll("`ComputedRef`", "[`ComputedRef`](https://vuejs.org/guide/typescript/composition-api.html#typing-computed)")
|
||||
.replaceAll(/# Defined in\n\nprofectus(\/src\/[^:]+):(\d+)/gm, "# Defined in\n\n[profectus$1:$2](https://code.incremental.social/profectus/Profectus/src/branch/main$1#L$2)");
|
||||
const fd = fs.openSync(filePath, "w+");
|
||||
fs.writeSync(fd, frontmatter);
|
||||
if (dir.includes("components")) {
|
||||
fs.writeSync(fd, `[Profectus](../../../index.md) / ${relPath.replaceAll("\\", "/").replaceAll("/components", "")} / ${path.basename(file).replace(".md", "")}\n\n`)
|
||||
}
|
||||
fs.writeSync(fd, data);
|
||||
fs.closeSync(fd);
|
||||
resolve();
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit d0fffd3b89aa6dbecf689bbb30cb0a6621afa24d
|
||||
Subproject commit 78394b83c4225f09405ec92eb6d291007c1230a7
|
|
@ -1,7 +0,0 @@
|
|||
import { Application } from 'typedoc';
|
||||
|
||||
import { ProfectusTheme } from './theme';
|
||||
|
||||
export function load(app: Application) {
|
||||
app.renderer.defineTheme('profectus', ProfectusTheme);
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,37 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('breadcrumbs', function () {
|
||||
const { entryPoints, entryDocument, project, readme } = theme;
|
||||
if (!project) {
|
||||
return '';
|
||||
}
|
||||
const hasReadmeFile = !readme.endsWith('none');
|
||||
const breadcrumbs = [];
|
||||
const globalsName = entryPoints.length > 1 ? 'Modules' : 'Exports';
|
||||
breadcrumbs.push(this.url === entryDocument
|
||||
? project.name
|
||||
: `[${project.name}](${Handlebars.helpers.relativeURL(entryDocument)})`);
|
||||
if (hasReadmeFile) {
|
||||
breadcrumbs.push(this.url === project.url
|
||||
? globalsName
|
||||
: `[${globalsName}](${Handlebars.helpers.relativeURL('modules.md')})`);
|
||||
}
|
||||
const breadcrumbsOut = breadcrumb(this, this.model, breadcrumbs);
|
||||
return breadcrumbsOut;
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
||||
function breadcrumb(page, model, md) {
|
||||
if (model && model.parent) {
|
||||
breadcrumb(page, model.parent, md);
|
||||
if (model.url) {
|
||||
md.push(page.url === model.url
|
||||
? `${(0, utils_1.escapeChars)(model.name)}`
|
||||
: `[${(0, utils_1.escapeChars)(model.name)}](${Handlebars.helpers.relativeURL(model.url)})`);
|
||||
}
|
||||
}
|
||||
return md.join(' / ');
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
||||
export declare function readFile(file: string): string;
|
|
@ -1,106 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.readFile = void 0;
|
||||
const fs = require("fs");
|
||||
const Handlebars = require("handlebars");
|
||||
const Path = require("path");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('comment', function (parts) {
|
||||
const result = [];
|
||||
for (const part of parts) {
|
||||
switch (part.kind) {
|
||||
case 'text':
|
||||
case 'code':
|
||||
result.push(part.text);
|
||||
break;
|
||||
case 'inline-tag':
|
||||
switch (part.tag) {
|
||||
case '@label':
|
||||
case '@inheritdoc':
|
||||
break;
|
||||
case '@link':
|
||||
case '@linkcode':
|
||||
case '@linkplain': {
|
||||
if (part.target) {
|
||||
const url = typeof part.target === 'string'
|
||||
? part.target
|
||||
: Handlebars.helpers.relativeURL(part.target.url);
|
||||
const wrap = part.tag === '@linkcode' ? '`' : '';
|
||||
result.push(url ? `[${wrap}${part.text}${wrap}](${url})` : part.text);
|
||||
}
|
||||
else {
|
||||
result.push(part.text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
result.push(`{${part.tag} ${part.text}}`);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result.push('');
|
||||
}
|
||||
}
|
||||
return parseMarkdown(result.join(''), theme);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
||||
function parseMarkdown(text, theme) {
|
||||
const includePattern = /\[\[include:([^\]]+?)\]\]/g;
|
||||
const mediaPattern = /media:\/\/([^ ")\]}]+)/g;
|
||||
if (theme.includes) {
|
||||
text = text.replace(includePattern, (_match, path) => {
|
||||
path = Path.join(theme.includes, path.trim());
|
||||
if (fs.existsSync(path) && fs.statSync(path).isFile()) {
|
||||
const contents = readFile(path);
|
||||
return contents;
|
||||
}
|
||||
else {
|
||||
theme.application.logger.warn('Could not find file to include: ' + path);
|
||||
return '';
|
||||
}
|
||||
});
|
||||
}
|
||||
if (theme.mediaDirectory) {
|
||||
text = text.replace(mediaPattern, (match, path) => {
|
||||
const fileName = Path.join(theme.mediaDirectory, path);
|
||||
if (fs.existsSync(fileName) && fs.statSync(fileName).isFile()) {
|
||||
return Handlebars.helpers.relativeURL('media') + '/' + path;
|
||||
}
|
||||
else {
|
||||
theme.application.logger.warn('Could not find media file: ' + fileName);
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
return text;
|
||||
}
|
||||
function readFile(file) {
|
||||
const buffer = fs.readFileSync(file);
|
||||
switch (buffer[0]) {
|
||||
case 0xfe:
|
||||
if (buffer[1] === 0xff) {
|
||||
let i = 0;
|
||||
while (i + 1 < buffer.length) {
|
||||
const temp = buffer[i];
|
||||
buffer[i] = buffer[i + 1];
|
||||
buffer[i + 1] = temp;
|
||||
i += 2;
|
||||
}
|
||||
return buffer.toString('ucs2', 2);
|
||||
}
|
||||
break;
|
||||
case 0xff:
|
||||
if (buffer[1] === 0xfe) {
|
||||
return buffer.toString('ucs2', 2);
|
||||
}
|
||||
break;
|
||||
case 0xef:
|
||||
if (buffer[1] === 0xbb) {
|
||||
return buffer.toString('utf8', 3);
|
||||
}
|
||||
}
|
||||
return buffer.toString('utf8', 0);
|
||||
}
|
||||
exports.readFile = readFile;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,21 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('comments', function (comment) {
|
||||
var _a;
|
||||
const md = [];
|
||||
if (comment.summary) {
|
||||
md.push(Handlebars.helpers.comment(comment.summary));
|
||||
}
|
||||
if ((_a = comment.blockTags) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
const tags = comment.blockTags
|
||||
.filter((tag) => tag.tag !== '@returns')
|
||||
.map((tag) => `**\`${(0, utils_1.camelToTitleCase)(tag.tag.substring(1))}\`**\n\n${Handlebars.helpers.comment(tag.content)}`);
|
||||
md.push(tags.join('\n\n'));
|
||||
}
|
||||
return md.join('\n\n');
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,36 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('declarationTitle', function () {
|
||||
const md = theme.hideMembersSymbol ? [] : [(0, utils_1.memberSymbol)(this)];
|
||||
function getType(reflection) {
|
||||
var _a, _b;
|
||||
const reflectionType = reflection.type;
|
||||
if (reflectionType && ((_a = reflectionType.declaration) === null || _a === void 0 ? void 0 : _a.children)) {
|
||||
return ': `Object`';
|
||||
}
|
||||
return ((((_b = reflection.parent) === null || _b === void 0 ? void 0 : _b.kindOf(typedoc_1.ReflectionKind.Enum)) ? ' = ' : ': ') +
|
||||
Handlebars.helpers.type.call(reflectionType ? reflectionType : reflection, 'object'));
|
||||
}
|
||||
if (this.flags && this.flags.length > 0 && !this.flags.isRest) {
|
||||
md.push(' ' + this.flags.map((flag) => `\`${flag}\``).join(' '));
|
||||
}
|
||||
md.push(`${this.flags.isRest ? '... ' : ''} **${(0, utils_1.escapeChars)(this.name)}**`);
|
||||
if (this instanceof typedoc_1.DeclarationReflection && this.typeParameters) {
|
||||
md.push(`<${this.typeParameters
|
||||
.map((typeParameter) => `\`${typeParameter.name}\``)
|
||||
.join(', ')}\\>`);
|
||||
}
|
||||
md.push(getType(this));
|
||||
if (!(this.type instanceof typedoc_1.LiteralType) &&
|
||||
this.defaultValue &&
|
||||
this.defaultValue !== '...') {
|
||||
md.push(` = \`${(0, utils_1.stripLineBreaks)((0, utils_1.stripComments)(this.defaultValue))}\``);
|
||||
}
|
||||
return md.join('');
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,10 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('escape', function (str) {
|
||||
return (0, utils_1.escapeChars)(str);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,26 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('hierarchy', function (level) {
|
||||
const md = [];
|
||||
const symbol = level > 0 ? getSymbol(level) : '-';
|
||||
this.types.forEach((hierarchyType) => {
|
||||
if (this.isTarget) {
|
||||
md.push(`${symbol} **\`${hierarchyType}\`**`);
|
||||
}
|
||||
else {
|
||||
md.push(`${symbol} ${Handlebars.helpers.type.call(hierarchyType)}`);
|
||||
}
|
||||
});
|
||||
if (this.next) {
|
||||
md.push(Handlebars.helpers.hierarchy.call(this.next, level + 1));
|
||||
}
|
||||
return md.join('\n\n');
|
||||
});
|
||||
function getSymbol(level) {
|
||||
return (0, utils_1.spaces)(2) + [...Array(level)].map(() => '↳').join('');
|
||||
}
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,12 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('ifIsReference', function (options) {
|
||||
return this instanceof typedoc_1.ReferenceReflection
|
||||
? options.fn(this)
|
||||
: options.inverse(this);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,9 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('ifNamedAnchors', function (options) {
|
||||
return theme.namedAnchors ? options.fn(this) : options.inverse(this);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,9 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('ifShowBreadcrumbs', function (options) {
|
||||
return theme.hideBreadcrumbs ? options.inverse(this) : options.fn(this);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,9 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('ifShowNamedAnchors', function (options) {
|
||||
return theme.namedAnchors ? options.fn(this) : options.inverse(this);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,9 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('ifShowPageTitle', function (options) {
|
||||
return theme.hidePageTitle ? options.inverse(this) : options.fn(this);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,13 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('ifShowReturns', function (options) {
|
||||
var _a;
|
||||
return this.type && !((_a = this.parent) === null || _a === void 0 ? void 0 : _a.kindOf(typedoc_1.ReflectionKind.Constructor))
|
||||
? options.fn(this)
|
||||
: options.inverse(this);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,13 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('ifShowTypeHierarchy', function (options) {
|
||||
var _a;
|
||||
const typeHierarchy = (_a = this.model) === null || _a === void 0 ? void 0 : _a.typeHierarchy;
|
||||
return typeHierarchy && typeHierarchy.next
|
||||
? options.fn(this)
|
||||
: options.inverse(this);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,16 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('indexSignatureTitle', function () {
|
||||
const md = ['▪'];
|
||||
const parameters = this.parameters
|
||||
? this.parameters.map((parameter) => {
|
||||
return `${parameter.name}: ${Handlebars.helpers.type.call(parameter.type)}`;
|
||||
})
|
||||
: [];
|
||||
md.push(`\[${parameters.join('')}\]: ${Handlebars.helpers.type.call(this.type)}`);
|
||||
return md.join(' ');
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,100 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
const utils_1 = require("../../utils");
|
||||
const type_1 = require("./type");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('parameterTable', function () {
|
||||
const flattenParams = (current) => {
|
||||
var _a, _b, _c;
|
||||
return (_c = (_b = (_a = current.type) === null || _a === void 0 ? void 0 : _a.declaration) === null || _b === void 0 ? void 0 : _b.children) === null || _c === void 0 ? void 0 : _c.reduce((acc, child) => {
|
||||
const childObj = {
|
||||
...child,
|
||||
name: `${current.name}.${child.name}`,
|
||||
};
|
||||
return parseParams(childObj, acc);
|
||||
}, []);
|
||||
};
|
||||
const parseParams = (current, acc) => {
|
||||
var _a, _b, _c, _d;
|
||||
const shouldFlatten = ((_b = (_a = current.type) === null || _a === void 0 ? void 0 : _a.declaration) === null || _b === void 0 ? void 0 : _b.kind) === typedoc_1.ReflectionKind.TypeLiteral &&
|
||||
((_d = (_c = current.type) === null || _c === void 0 ? void 0 : _c.declaration) === null || _d === void 0 ? void 0 : _d.children);
|
||||
return shouldFlatten
|
||||
? [...acc, current, ...flattenParams(current)]
|
||||
: [...acc, current];
|
||||
};
|
||||
return table(this.reduce((acc, current) => parseParams(current, acc), []));
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
||||
function table(parameters) {
|
||||
const showDefaults = hasDefaultValues(parameters);
|
||||
const comments = parameters.map((param) => { var _a; return !!((_a = param.comment) === null || _a === void 0 ? void 0 : _a.hasVisibleComponent()); });
|
||||
const hasComments = !comments.every((value) => !value);
|
||||
const headers = ['Name', 'Type'];
|
||||
if (showDefaults) {
|
||||
headers.push('Default value');
|
||||
}
|
||||
if (hasComments) {
|
||||
headers.push('Description');
|
||||
}
|
||||
const rows = parameters.map((parameter) => {
|
||||
const row = [];
|
||||
const nbsp = ' ';
|
||||
const rest = parameter.flags.isRest ? '...' : '';
|
||||
const optional = parameter.flags.isOptional ? '?' : '';
|
||||
const isDestructuredParam = parameter.name == '__namedParameters';
|
||||
const isDestructuredParamProp = parameter.name.startsWith('__namedParameters.');
|
||||
if (isDestructuredParam) {
|
||||
row.push(`\`${rest}«destructured»\``);
|
||||
}
|
||||
else if (isDestructuredParamProp) {
|
||||
row.push(`›${nbsp}\`${rest}${parameter.name.slice(18)}${optional}\``);
|
||||
}
|
||||
else {
|
||||
row.push(`\`${rest}${parameter.name}${optional}\``);
|
||||
}
|
||||
row.push(parameter.type
|
||||
? Handlebars.helpers.type.call(parameter.type, 'object')
|
||||
: (0, type_1.getReflectionType)(parameter, 'object'));
|
||||
if (showDefaults) {
|
||||
row.push(getDefaultValue(parameter));
|
||||
}
|
||||
if (hasComments) {
|
||||
const comments = getComments(parameter);
|
||||
if (comments) {
|
||||
row.push((0, utils_1.stripLineBreaks)(Handlebars.helpers.comments(comments)).replace(/\|/g, '\\|'));
|
||||
}
|
||||
else {
|
||||
row.push('-');
|
||||
}
|
||||
}
|
||||
return `| ${row.join(' | ')} |\n`;
|
||||
});
|
||||
const output = `\n| ${headers.join(' | ')} |\n| ${headers
|
||||
.map(() => ':------')
|
||||
.join(' | ')} |\n${rows.join('')}`;
|
||||
return output;
|
||||
}
|
||||
function getDefaultValue(parameter) {
|
||||
return parameter.defaultValue && parameter.defaultValue !== '...'
|
||||
? `\`${parameter.defaultValue}\``
|
||||
: '`undefined`';
|
||||
}
|
||||
function hasDefaultValues(parameters) {
|
||||
const defaultValues = parameters.map((param) => param.defaultValue !== '{}' &&
|
||||
param.defaultValue !== '...' &&
|
||||
!!param.defaultValue);
|
||||
return !defaultValues.every((value) => !value);
|
||||
}
|
||||
function getComments(parameter) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h;
|
||||
if (parameter.type instanceof typedoc_1.ReflectionType) {
|
||||
if (((_b = (_a = parameter.type) === null || _a === void 0 ? void 0 : _a.declaration) === null || _b === void 0 ? void 0 : _b.signatures) &&
|
||||
((_e = (_d = (_c = parameter.type) === null || _c === void 0 ? void 0 : _c.declaration) === null || _d === void 0 ? void 0 : _d.signatures[0]) === null || _e === void 0 ? void 0 : _e.comment)) {
|
||||
return (_h = (_g = (_f = parameter.type) === null || _f === void 0 ? void 0 : _f.declaration) === null || _g === void 0 ? void 0 : _g.signatures[0]) === null || _h === void 0 ? void 0 : _h.comment;
|
||||
}
|
||||
}
|
||||
return parameter.comment;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,16 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('referenceMember', function () {
|
||||
const referenced = this.tryGetTargetReflectionDeep();
|
||||
if (!referenced) {
|
||||
return `Re-exports ${this.name}`;
|
||||
}
|
||||
if (this.name === referenced.name) {
|
||||
return `Re-exports [${referenced.name}](${Handlebars.helpers.relativeURL(referenced.url)})`;
|
||||
}
|
||||
return `Renames and re-exports [${referenced.name}](${Handlebars.helpers.relativeURL(referenced.url)})`;
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,24 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('reflectionPath', function () {
|
||||
var _a, _b;
|
||||
if (this.model) {
|
||||
if (this.model.kind && this.model.kind !== typedoc_1.ReflectionKind.Module) {
|
||||
const title = [];
|
||||
if (this.model.parent && this.model.parent.parent) {
|
||||
if (this.model.parent.parent.parent) {
|
||||
title.push(`[${this.model.parent.parent.name}](${Handlebars.helpers.relativeURL((_b = (_a = this.model) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.parent.url)})`);
|
||||
}
|
||||
title.push(`[${this.model.parent.name}](${Handlebars.helpers.relativeURL(this.model.parent.url)})`);
|
||||
}
|
||||
title.push(this.model.name);
|
||||
return title.length > 1 ? `${title.join('.')}` : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,28 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('reflectionTitle', function (shouldEscape = true) {
|
||||
const title = [''];
|
||||
if (this.model &&
|
||||
this.model.kindString &&
|
||||
this.url !== this.project.url) {
|
||||
title.push(`${this.model.kindString}: `);
|
||||
}
|
||||
if (this.url === this.project.url) {
|
||||
title.push(theme.indexTitle || this.project.name);
|
||||
}
|
||||
else {
|
||||
title.push(shouldEscape ? (0, utils_1.escapeChars)(this.model.name) : this.model.name);
|
||||
if (this.model.typeParameters) {
|
||||
const typeParameters = this.model.typeParameters
|
||||
.map((typeParameter) => typeParameter.name)
|
||||
.join(', ');
|
||||
title.push(`<${typeParameters}${shouldEscape ? '\\>' : '>'}`);
|
||||
}
|
||||
}
|
||||
return title.join('');
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,13 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('relativeURL', function (url) {
|
||||
return url
|
||||
? theme.publicPath
|
||||
? theme.publicPath + url
|
||||
: theme.getRelativeUrl(url)
|
||||
: url;
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,17 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('returns', function (comment) {
|
||||
var _a;
|
||||
const md = [];
|
||||
if ((_a = comment.blockTags) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
const tags = comment.blockTags
|
||||
.filter((tag) => tag.tag === '@returns')
|
||||
.map((tag) => Handlebars.helpers.comment(tag.content));
|
||||
md.push(tags.join('\n\n'));
|
||||
}
|
||||
return md.join('');
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,43 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('signatureTitle', function (accessor, standalone = true) {
|
||||
var _a, _b;
|
||||
const md = [];
|
||||
if (standalone && !theme.hideMembersSymbol) {
|
||||
md.push(`${(0, utils_1.memberSymbol)(this)} `);
|
||||
}
|
||||
if (this.parent && ((_a = this.parent.flags) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
||||
md.push(this.parent.flags.map((flag) => `\`${flag}\``).join(' ') + ' ');
|
||||
}
|
||||
if (accessor) {
|
||||
md.push(`\`${accessor}\` **${this.name}**`);
|
||||
}
|
||||
else if (this.name !== '__call' && this.name !== '__type') {
|
||||
md.push(`**${this.name}**`);
|
||||
}
|
||||
if (this.typeParameters) {
|
||||
md.push(`<${this.typeParameters
|
||||
.map((typeParameter) => `\`${typeParameter.name}\``)
|
||||
.join(', ')}\\>`);
|
||||
}
|
||||
md.push(`(${getParameters(this.parameters)})`);
|
||||
if (this.type && !((_b = this.parent) === null || _b === void 0 ? void 0 : _b.kindOf(typedoc_1.ReflectionKind.Constructor))) {
|
||||
md.push(`: ${Handlebars.helpers.type.call(this.type, 'object')}`);
|
||||
}
|
||||
return md.join('') + (standalone ? '\n' : '');
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
||||
const getParameters = (parameters = [], backticks = true) => {
|
||||
return parameters
|
||||
.map((param) => {
|
||||
const isDestructuredParam = param.name == '__namedParameters';
|
||||
const paramItem = `${param.flags.isRest ? '...' : ''}${isDestructuredParam ? '«destructured»' : param.name}${param.flags.isOptional || param.defaultValue ? '?' : ''}`;
|
||||
return backticks ? `\`${paramItem}\`` : paramItem;
|
||||
})
|
||||
.join(', ');
|
||||
};
|
2
profectus-theme/resources/helpers/toc.d.ts
vendored
2
profectus-theme/resources/helpers/toc.d.ts
vendored
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,41 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('toc', function () {
|
||||
var _a, _b;
|
||||
const md = [];
|
||||
const { hideInPageTOC } = theme;
|
||||
const isVisible = (_a = this.groups) === null || _a === void 0 ? void 0 : _a.some((group) => group.allChildrenHaveOwnDocument());
|
||||
function pushGroup(group, md) {
|
||||
const children = group.children.map((child) => `- [${(0, utils_1.escapeChars)(child.name)}](${Handlebars.helpers.relativeURL(child.url)})`);
|
||||
md.push(children.join('\n'));
|
||||
}
|
||||
if ((!hideInPageTOC && this.groups) || (isVisible && this.groups)) {
|
||||
if (!hideInPageTOC) {
|
||||
md.push(`## Table of contents\n\n`);
|
||||
}
|
||||
const headingLevel = hideInPageTOC ? `##` : `###`;
|
||||
(_b = this.groups) === null || _b === void 0 ? void 0 : _b.forEach((group) => {
|
||||
const groupTitle = group.title;
|
||||
if (group.categories) {
|
||||
group.categories.forEach((category) => {
|
||||
md.push(`${headingLevel} ${category.title} ${groupTitle}\n\n`);
|
||||
pushGroup(category, md);
|
||||
md.push('\n');
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (!hideInPageTOC || group.allChildrenHaveOwnDocument()) {
|
||||
md.push(`${headingLevel} ${groupTitle}\n\n`);
|
||||
pushGroup(group, md);
|
||||
md.push('\n');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return md.length > 0 ? md.join('\n') : null;
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,43 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('typeAndParent', function () {
|
||||
var _a, _b, _c;
|
||||
const getUrl = (name, url) => `[${name}](${Handlebars.helpers.relativeURL(url)})`;
|
||||
if (this) {
|
||||
if ('elementType' in this) {
|
||||
return Handlebars.helpers.typeAndParent.call(this.elementType) + '[]';
|
||||
}
|
||||
else {
|
||||
if (this.reflection) {
|
||||
const md = [];
|
||||
if (this.reflection instanceof typedoc_1.SignatureReflection) {
|
||||
if ((_b = (_a = this.reflection.parent) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.url) {
|
||||
md.push(getUrl(this.reflection.parent.parent.name, this.reflection.parent.parent.url));
|
||||
if (this.reflection.parent.url) {
|
||||
md.push(getUrl(this.reflection.parent.name, this.reflection.parent.url));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((_c = this.reflection.parent) === null || _c === void 0 ? void 0 : _c.url) {
|
||||
md.push(getUrl(this.reflection.parent.name, this.reflection.parent.url));
|
||||
if (this.reflection.url) {
|
||||
md.push(getUrl(this.reflection.name, this.reflection.url));
|
||||
}
|
||||
}
|
||||
}
|
||||
return md.join('.');
|
||||
}
|
||||
else {
|
||||
return (0, utils_1.escapeChars)(this.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'void';
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
|
@ -1,2 +0,0 @@
|
|||
import { MarkdownTheme } from '../../theme';
|
||||
export default function (theme: MarkdownTheme): void;
|
|
@ -1,151 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1(theme) {
|
||||
Handlebars.registerHelper('typeDeclarationMembers', function () {
|
||||
const comments = this.map((param) => { var _a; return !!((_a = param.comment) === null || _a === void 0 ? void 0 : _a.hasVisibleComponent()); });
|
||||
const hasComments = !comments.every((value) => !value);
|
||||
const flattenParams = (current) => {
|
||||
var _a, _b, _c;
|
||||
return (_c = (_b = (_a = current.type) === null || _a === void 0 ? void 0 : _a.declaration) === null || _b === void 0 ? void 0 : _b.children) === null || _c === void 0 ? void 0 : _c.reduce((acc, child) => {
|
||||
const childObj = {
|
||||
...child,
|
||||
name: `${current.name}.${child.name}`,
|
||||
};
|
||||
return parseParams(childObj, acc);
|
||||
}, []);
|
||||
};
|
||||
const parseParams = (current, acc) => {
|
||||
var _a, _b;
|
||||
const shouldFlatten = (_b = (_a = current.type) === null || _a === void 0 ? void 0 : _a.declaration) === null || _b === void 0 ? void 0 : _b.children;
|
||||
return shouldFlatten
|
||||
? [...acc, current, ...flattenParams(current)]
|
||||
: [...acc, current];
|
||||
};
|
||||
const properties = this.reduce((acc, current) => parseParams(current, acc), []);
|
||||
let result = '';
|
||||
switch (theme.objectLiteralTypeDeclarationStyle) {
|
||||
case 'list': {
|
||||
result = getListMarkdownContent(properties);
|
||||
break;
|
||||
}
|
||||
case 'table': {
|
||||
result = getTableMarkdownContent(properties, hasComments);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
||||
function getListMarkdownContent(properties) {
|
||||
const propertyTable = getTableMarkdownContentWithoutComment(properties);
|
||||
const propertyDescriptions = properties.map((property) => {
|
||||
const name = property.name.match(/[\\`\\|]/g) !== null
|
||||
? (0, utils_1.escapeChars)(getName(property))
|
||||
: `${getName(property)}`;
|
||||
const propertyType = getPropertyType(property);
|
||||
const propertyTypeStr = Handlebars.helpers.type.call(propertyType);
|
||||
const comments = getComments(property);
|
||||
const commentsStr = comments
|
||||
? Handlebars.helpers.comments(comments)
|
||||
: '\\-';
|
||||
const md = `**${name}**: ${propertyTypeStr}
|
||||
|
||||
${commentsStr}
|
||||
|
||||
-----
|
||||
|
||||
|
||||
`;
|
||||
return md;
|
||||
});
|
||||
const propertyComments = propertyDescriptions.join('\n\n');
|
||||
const result = `
|
||||
${propertyTable}
|
||||
|
||||
${propertyComments}
|
||||
|
||||
`;
|
||||
return result;
|
||||
}
|
||||
function getTableMarkdownContent(properties, hasComments) {
|
||||
const headers = ['Name', 'Type'];
|
||||
if (hasComments) {
|
||||
headers.push('Description');
|
||||
}
|
||||
const rows = properties.map((property) => {
|
||||
const propertyType = getPropertyType(property);
|
||||
const row = [];
|
||||
const nameCol = [];
|
||||
const name = property.name.match(/[\\`\\|]/g) !== null
|
||||
? (0, utils_1.escapeChars)(getName(property))
|
||||
: `\`${getName(property)}\``;
|
||||
nameCol.push(name);
|
||||
row.push(nameCol.join(' '));
|
||||
row.push(Handlebars.helpers.type.call(propertyType).replace(/(?<!\\)\|/g, '\\|'));
|
||||
if (hasComments) {
|
||||
const comments = getComments(property);
|
||||
if (comments) {
|
||||
row.push((0, utils_1.stripLineBreaks)(Handlebars.helpers.comments(comments)).replace(/\|/g, '\\|'));
|
||||
}
|
||||
else {
|
||||
row.push('-');
|
||||
}
|
||||
}
|
||||
return `| ${row.join(' | ')} |\n`;
|
||||
});
|
||||
const output = `\n| ${headers.join(' | ')} |\n| ${headers
|
||||
.map(() => ':------')
|
||||
.join(' | ')} |\n${rows.join('')}`;
|
||||
return output;
|
||||
}
|
||||
function getTableMarkdownContentWithoutComment(properties) {
|
||||
const result = getTableMarkdownContent(properties, false);
|
||||
return result;
|
||||
}
|
||||
function getPropertyType(property) {
|
||||
if (property.getSignature) {
|
||||
return property.getSignature.type;
|
||||
}
|
||||
if (property.setSignature) {
|
||||
return property.setSignature.type;
|
||||
}
|
||||
return property.type ? property.type : property;
|
||||
}
|
||||
function getName(property) {
|
||||
var _a;
|
||||
const md = [];
|
||||
if (property.flags.isRest) {
|
||||
md.push('...');
|
||||
}
|
||||
if (property.getSignature) {
|
||||
md.push(`get ${property.getSignature.name}()`);
|
||||
}
|
||||
else if (property.setSignature) {
|
||||
md.push(`set ${property.setSignature.name}(${(_a = property.setSignature.parameters) === null || _a === void 0 ? void 0 : _a.map((parameter) => {
|
||||
return `${parameter.name}:${Handlebars.helpers.type.call(parameter.type, 'all', false)}`;
|
||||
})})`);
|
||||
}
|
||||
else {
|
||||
md.push(property.name);
|
||||
}
|
||||
if (property.flags.isOptional) {
|
||||
md.push('?');
|
||||
}
|
||||
return md.join('');
|
||||
}
|
||||
function getComments(property) {
|
||||
var _a, _b, _c, _d;
|
||||
if (property.type instanceof typedoc_1.ReflectionType) {
|
||||
if ((_b = (_a = property.type) === null || _a === void 0 ? void 0 : _a.declaration) === null || _b === void 0 ? void 0 : _b.signatures) {
|
||||
return (_c = property.type) === null || _c === void 0 ? void 0 : _c.declaration.signatures[0].comment;
|
||||
}
|
||||
}
|
||||
if ((_d = property.signatures) === null || _d === void 0 ? void 0 : _d.length) {
|
||||
return property.signatures[0].comment;
|
||||
}
|
||||
return property.comment;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export default function (): void;
|
|
@ -1,60 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Handlebars = require("handlebars");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('typeParameterTable', function () {
|
||||
return table(this);
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
||||
function table(parameters) {
|
||||
const showTypeCol = hasTypes(parameters);
|
||||
const comments = parameters.map((param) => { var _a; return !!((_a = param.comment) === null || _a === void 0 ? void 0 : _a.hasVisibleComponent()); });
|
||||
const hasComments = !comments.every((value) => !value);
|
||||
const headers = ['Name'];
|
||||
if (showTypeCol) {
|
||||
headers.push('Type');
|
||||
}
|
||||
if (hasComments) {
|
||||
headers.push('Description');
|
||||
}
|
||||
const rows = parameters.map((parameter) => {
|
||||
var _a, _b;
|
||||
const row = [];
|
||||
row.push(`\`${parameter.name}\``);
|
||||
if (showTypeCol) {
|
||||
const typeCol = [];
|
||||
if (!parameter.type && !parameter.default) {
|
||||
typeCol.push(`\`${parameter.name}\``);
|
||||
}
|
||||
if (parameter.type) {
|
||||
typeCol.push(`extends ${Handlebars.helpers.type.call(parameter.type, 'object')}`);
|
||||
}
|
||||
if (parameter.default) {
|
||||
if (parameter.type) {
|
||||
typeCol.push(' = ');
|
||||
}
|
||||
typeCol.push(Handlebars.helpers.type.call(parameter.default));
|
||||
}
|
||||
row.push(typeCol.join(''));
|
||||
}
|
||||
if (hasComments) {
|
||||
if ((_a = parameter.comment) === null || _a === void 0 ? void 0 : _a.summary) {
|
||||
row.push((0, utils_1.stripLineBreaks)(Handlebars.helpers.comment((_b = parameter.comment) === null || _b === void 0 ? void 0 : _b.summary)).replace(/\|/g, '\\|'));
|
||||
}
|
||||
else {
|
||||
row.push('-');
|
||||
}
|
||||
}
|
||||
return `| ${row.join(' | ')} |\n`;
|
||||
});
|
||||
const output = `\n| ${headers.join(' | ')} |\n| ${headers
|
||||
.map(() => ':------')
|
||||
.join(' | ')} |\n${rows.join('')}`;
|
||||
return output;
|
||||
}
|
||||
function hasTypes(parameters) {
|
||||
const types = parameters.map((param) => !!param.type || !!param.default);
|
||||
return !types.every((value) => !value);
|
||||
}
|
6
profectus-theme/resources/helpers/type.d.ts
vendored
6
profectus-theme/resources/helpers/type.d.ts
vendored
|
@ -1,6 +0,0 @@
|
|||
import { DeclarationReflection, ReflectionType, SignatureReflection } from 'typedoc';
|
||||
type Collapse = 'object' | 'function' | 'all' | 'none';
|
||||
export default function (): void;
|
||||
export declare function getReflectionType(model: DeclarationReflection | ReflectionType, collapse: Collapse): string;
|
||||
export declare function getFunctionType(modelSignatures: SignatureReflection[]): string;
|
||||
export {};
|
|
@ -1,206 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getFunctionType = exports.getReflectionType = void 0;
|
||||
const Handlebars = require("handlebars");
|
||||
const typedoc_1 = require("typedoc");
|
||||
const utils_1 = require("../../utils");
|
||||
function default_1() {
|
||||
Handlebars.registerHelper('type', function (collapse = 'none', emphasis = true) {
|
||||
if (this instanceof typedoc_1.ReferenceType) {
|
||||
return getReferenceType(this, emphasis);
|
||||
}
|
||||
if (this instanceof typedoc_1.ArrayType && this.elementType) {
|
||||
return getArrayType(this, emphasis);
|
||||
}
|
||||
if (this instanceof typedoc_1.UnionType && this.types) {
|
||||
return getUnionType(this, emphasis);
|
||||
}
|
||||
if (this instanceof typedoc_1.IntersectionType && this.types) {
|
||||
return getIntersectionType(this);
|
||||
}
|
||||
if (this instanceof typedoc_1.TupleType && this.elements) {
|
||||
return getTupleType(this);
|
||||
}
|
||||
if (this instanceof typedoc_1.IntrinsicType && this.name) {
|
||||
return getIntrinsicType(this, emphasis);
|
||||
}
|
||||
if (this instanceof typedoc_1.ReflectionType) {
|
||||
return getReflectionType(this, collapse);
|
||||
}
|
||||
if (this instanceof typedoc_1.DeclarationReflection) {
|
||||
return getReflectionType(this, collapse);
|
||||
}
|
||||
if (this instanceof typedoc_1.TypeOperatorType) {
|
||||
return getTypeOperatorType(this);
|
||||
}
|
||||
if (this instanceof typedoc_1.QueryType) {
|
||||
return getQueryType(this);
|
||||
}
|
||||
if (this instanceof typedoc_1.ConditionalType) {
|
||||
return getConditionalType(this);
|
||||
}
|
||||
if (this instanceof typedoc_1.IndexedAccessType) {
|
||||
return getIndexAccessType(this);
|
||||
}
|
||||
if (this instanceof typedoc_1.UnknownType) {
|
||||
return getUnknownType(this);
|
||||
}
|
||||
if (this instanceof typedoc_1.InferredType) {
|
||||
return getInferredType(this);
|
||||
}
|
||||
if (this instanceof typedoc_1.LiteralType) {
|
||||
return getLiteralType(this);
|
||||
}
|
||||
return this ? (0, utils_1.escapeChars)(this.toString()) : '';
|
||||
});
|
||||
}
|
||||
exports.default = default_1;
|
||||
function getLiteralType(model) {
|
||||
if (typeof model.value === 'bigint') {
|
||||
return `\`${model.value}n\``;
|
||||
}
|
||||
return `\`\`${JSON.stringify(model.value)}\`\``;
|
||||
}
|
||||
function getReflectionType(model, collapse) {
|
||||
const root = model instanceof typedoc_1.ReflectionType ? model.declaration : model;
|
||||
if (root.signatures) {
|
||||
return collapse === 'function' || collapse === 'all'
|
||||
? `\`fn\``
|
||||
: getFunctionType(root.signatures);
|
||||
}
|
||||
return collapse === 'object' || collapse === 'all'
|
||||
? `\`Object\``
|
||||
: getDeclarationType(root);
|
||||
}
|
||||
exports.getReflectionType = getReflectionType;
|
||||
function getDeclarationType(model) {
|
||||
if (model.indexSignature || model.children) {
|
||||
let indexSignature = '';
|
||||
const declarationIndexSignature = model.indexSignature;
|
||||
if (declarationIndexSignature) {
|
||||
const key = declarationIndexSignature.parameters
|
||||
? declarationIndexSignature.parameters.map((param) => `\`[${param.name}: ${param.type}]\``)
|
||||
: '';
|
||||
const obj = Handlebars.helpers.type.call(declarationIndexSignature.type);
|
||||
indexSignature = `${key}: ${obj}; `;
|
||||
}
|
||||
const types = model.children &&
|
||||
model.children.map((obj) => {
|
||||
return `\`${obj.name}${obj.flags.isOptional ? '?' : ''}\`: ${Handlebars.helpers.type.call(obj.signatures || obj.children ? obj : obj.type)} ${obj.defaultValue && obj.defaultValue !== '...'
|
||||
? `= ${(0, utils_1.escapeChars)(obj.defaultValue)}`
|
||||
: ''}`;
|
||||
});
|
||||
return `{ ${indexSignature ? indexSignature : ''}${types ? types.join('; ') : ''} }${model.defaultValue && model.defaultValue !== '...'
|
||||
? `= ${(0, utils_1.escapeChars)(model.defaultValue)}`
|
||||
: ''}`;
|
||||
}
|
||||
return '{}';
|
||||
}
|
||||
function getFunctionType(modelSignatures) {
|
||||
const functions = modelSignatures.map((fn) => {
|
||||
const typeParams = fn.typeParameters
|
||||
? `<${fn.typeParameters
|
||||
.map((typeParameter) => typeParameter.name)
|
||||
.join(', ')}\\>`
|
||||
: [];
|
||||
const params = fn.parameters
|
||||
? fn.parameters.map((param) => {
|
||||
return `${param.flags.isRest ? '...' : ''}\`${param.name}${param.flags.isOptional ? '?' : ''}\`: ${Handlebars.helpers.type.call(param.type ? param.type : param)}`;
|
||||
})
|
||||
: [];
|
||||
const returns = Handlebars.helpers.type.call(fn.type);
|
||||
return typeParams + `(${params.join(', ')}) => ${returns}`;
|
||||
});
|
||||
return functions.join('');
|
||||
}
|
||||
exports.getFunctionType = getFunctionType;
|
||||
function getReferenceType(model, emphasis) {
|
||||
var _a;
|
||||
if (model.reflection || (model.name && model.typeArguments)) {
|
||||
const reflection = [];
|
||||
if ((_a = model.reflection) === null || _a === void 0 ? void 0 : _a.url) {
|
||||
reflection.push(`[${`\`${model.reflection.name}\``}](${Handlebars.helpers.relativeURL(model.reflection.url)})`);
|
||||
}
|
||||
else {
|
||||
reflection.push(model.externalUrl
|
||||
? `[${`\`${model.name}\``}]( ${model.externalUrl} )`
|
||||
: `\`${model.name}\``);
|
||||
}
|
||||
if (model.typeArguments && model.typeArguments.length > 0) {
|
||||
reflection.push(`<${model.typeArguments
|
||||
.map((typeArgument) => Handlebars.helpers.type.call(typeArgument))
|
||||
.join(', ')}\\>`);
|
||||
}
|
||||
return reflection.join('');
|
||||
}
|
||||
return emphasis
|
||||
? model.externalUrl
|
||||
? `[${`\`${model.name}\``}]( ${model.externalUrl} )`
|
||||
: `\`${model.name}\``
|
||||
: (0, utils_1.escapeChars)(model.name);
|
||||
}
|
||||
function getArrayType(model, emphasis) {
|
||||
const arrayType = Handlebars.helpers.type.call(model.elementType, 'none', emphasis);
|
||||
return model.elementType.type === 'union'
|
||||
? `(${arrayType})[]`
|
||||
: `${arrayType}[]`;
|
||||
}
|
||||
function getUnionType(model, emphasis) {
|
||||
return model.types
|
||||
.map((unionType) => Handlebars.helpers.type.call(unionType, 'none', emphasis))
|
||||
.join(` \\| `);
|
||||
}
|
||||
function getIntersectionType(model) {
|
||||
return model.types
|
||||
.map((intersectionType) => Handlebars.helpers.type.call(intersectionType))
|
||||
.join(' & ');
|
||||
}
|
||||
function getTupleType(model) {
|
||||
return `[${model.elements
|
||||
.map((element) => Handlebars.helpers.type.call(element))
|
||||
.join(', ')}]`;
|
||||
}
|
||||
function getIntrinsicType(model, emphasis) {
|
||||
return emphasis ? `\`${model.name}\`` : (0, utils_1.escapeChars)(model.name);
|
||||
}
|
||||
function getTypeOperatorType(model) {
|
||||
return `${model.operator} ${Handlebars.helpers.type.call(model.target)}`;
|
||||
}
|
||||
function getQueryType(model) {
|
||||
return `typeof ${Handlebars.helpers.type.call(model.queryType)}`;
|
||||
}
|
||||
function getInferredType(model) {
|
||||
return `infer ${(0, utils_1.escapeChars)(model.name)}`;
|
||||
}
|
||||
function getUnknownType(model) {
|
||||
return (0, utils_1.escapeChars)(model.name);
|
||||
}
|
||||
function getConditionalType(model) {
|
||||
const md = [];
|
||||
if (model.checkType) {
|
||||
md.push(Handlebars.helpers.type.call(model.checkType));
|
||||
}
|
||||
md.push('extends');
|
||||
if (model.extendsType) {
|
||||
md.push(Handlebars.helpers.type.call(model.extendsType));
|
||||
}
|
||||
md.push('?');
|
||||
if (model.trueType) {
|
||||
md.push(Handlebars.helpers.type.call(model.trueType));
|
||||
}
|
||||
md.push(':');
|
||||
if (model.falseType) {
|
||||
md.push(Handlebars.helpers.type.call(model.falseType));
|
||||
}
|
||||
return md.join(' ');
|
||||
}
|
||||
function getIndexAccessType(model) {
|
||||
const md = [];
|
||||
if (model.objectType) {
|
||||
md.push(Handlebars.helpers.type.call(model.objectType));
|
||||
}
|
||||
if (model.indexType) {
|
||||
md.push(`[${Handlebars.helpers.type.call(model.indexType)}]`);
|
||||
}
|
||||
return md.join('');
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{{#with comment}}
|
||||
|
||||
{{#if hasVisibleComponent}}
|
||||
|
||||
{{{comments this}}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
|
@ -1,5 +0,0 @@
|
|||
{{#ifShowBreadcrumbs}}
|
||||
|
||||
{{{breadcrumbs}}}
|
||||
|
||||
{{/ifShowBreadcrumbs}}
|
|
@ -1,3 +0,0 @@
|
|||
{{ toc }}
|
||||
|
||||
{{> members}}
|
|
@ -1,101 +0,0 @@
|
|||
{{{declarationTitle}}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{#if typeParameters}}
|
||||
|
||||
#### Type parameters
|
||||
|
||||
{{#with typeParameters}}
|
||||
|
||||
{{{typeParameterTable}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if type.declaration}}
|
||||
|
||||
{{#if type.declaration.indexSignature}}
|
||||
|
||||
{{#with type.declaration.indexSignature}}
|
||||
|
||||
#### Index signature
|
||||
|
||||
{{{indexSignatureTitle}}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if type.declaration.signatures}}
|
||||
|
||||
{{#if type.declaration.children}}
|
||||
|
||||
#### Call signature
|
||||
|
||||
{{else}}
|
||||
|
||||
#### Type declaration
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#each type.declaration.signatures}}
|
||||
|
||||
{{> member.signature showSources=false }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if type.declaration.children}}
|
||||
|
||||
{{#with type.declaration}}
|
||||
|
||||
#### Type declaration
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with type.declaration.children}}
|
||||
|
||||
{{{typeDeclarationMembers}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#when this this.kindString 'eq' 'Interface'}}
|
||||
|
||||
{{#with this.children}}
|
||||
|
||||
{{{typeDeclarationMembers}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/when}}
|
||||
|
||||
{{#when this this.kindString 'eq' 'Class'}}
|
||||
|
||||
{{#with this.children}}
|
||||
|
||||
{{{typeDeclarationMembers}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/when}}
|
||||
|
||||
{{#when this this.kindString 'eq' 'Enumeration'}}
|
||||
|
||||
{{#with this.children}}
|
||||
|
||||
{{{typeDeclarationMembers}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/when}}
|
||||
|
||||
{{> member.sources}}
|
|
@ -1,19 +0,0 @@
|
|||
{{#if getSignature}}
|
||||
|
||||
{{#with getSignature}}
|
||||
|
||||
{{> member.signature accessor="get" showSources=true }}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if setSignature}}
|
||||
|
||||
{{#with setSignature}}
|
||||
|
||||
{{> member.signature accessor="set" showSources=true }}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
|
@ -1,48 +0,0 @@
|
|||
{{#unless hasOwnDocument}}
|
||||
|
||||
{{#if name}}
|
||||
|
||||
### {{#ifNamedAnchors}}<a id="{{anchor}}" name="{{this.anchor}}"></a> {{/ifNamedAnchors}}{{ escape name }}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/unless}}
|
||||
|
||||
{{#if signatures}}
|
||||
|
||||
{{#each signatures}}
|
||||
|
||||
{{> member.signature showSources=false }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{> member.sources}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#if hasGetterOrSetter}}
|
||||
|
||||
|
||||
{{> member.getterSetter}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#ifIsReference}}
|
||||
|
||||
{{referenceMember}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{> member.declaration}}
|
||||
|
||||
{{/ifIsReference}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#unless @last}}
|
||||
{{#unless hasOwnDocument}}
|
||||
___
|
||||
{{/unless}}
|
||||
{{/unless}}
|
|
@ -1,127 +0,0 @@
|
|||
{{#if comment}}
|
||||
|
||||
{{{signatureTitle accessor}}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{#if typeParameters}}
|
||||
|
||||
{{#if showSources}}
|
||||
|
||||
#### Type parameters
|
||||
|
||||
{{else}}
|
||||
|
||||
##### Type parameters
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#with typeParameters}}
|
||||
|
||||
{{{typeParameterTable}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if parameters}}
|
||||
|
||||
{{#if showSources}}
|
||||
|
||||
#### Parameters
|
||||
|
||||
{{else}}
|
||||
|
||||
##### Parameters
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#with parameters}}
|
||||
|
||||
{{{parameterTable}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#ifShowReturns}}
|
||||
|
||||
{{#if type}}
|
||||
|
||||
{{#if showSources}}
|
||||
|
||||
#### Returns
|
||||
|
||||
{{else}}
|
||||
|
||||
##### Returns
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#with type}}
|
||||
|
||||
{{{type 'all'}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with comment}}
|
||||
|
||||
{{{returns this}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#with type}}
|
||||
|
||||
{{#if declaration.signatures}}
|
||||
|
||||
{{#each declaration.signatures}}
|
||||
|
||||
{{> member.signature showSources=false }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if declaration.children}}
|
||||
|
||||
{{#with declaration.children}}
|
||||
|
||||
{{{typeDeclarationMembers}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/ifShowReturns}}
|
||||
|
||||
{{#if showSources}}
|
||||
|
||||
{{> member.sources}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if @first}}
|
||||
|
||||
{{#unless @last}}
|
||||
|
||||
#### Other signatures
|
||||
|
||||
{{/unless}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{{signatureTitle accessor}}}
|
||||
|
||||
{{#with parameters}}
|
||||
|
||||
{{{parameterTable}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
|
@ -1,55 +0,0 @@
|
|||
{{#if implementationOf}}
|
||||
|
||||
#### Implementation of
|
||||
|
||||
{{#with implementationOf}}
|
||||
|
||||
{{typeAndParent}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if inheritedFrom}}
|
||||
|
||||
#### Inherited from
|
||||
|
||||
{{#with inheritedFrom}}
|
||||
|
||||
{{{typeAndParent}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if overwrites}}
|
||||
|
||||
#### Overrides
|
||||
|
||||
{{#with overwrites}}
|
||||
|
||||
{{typeAndParent}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if sources}}
|
||||
|
||||
#### Defined in
|
||||
|
||||
{{#each sources}}
|
||||
|
||||
{{#if url}}
|
||||
|
||||
[{{fileName}}:{{line}}]({{url}})
|
||||
|
||||
{{else}}
|
||||
|
||||
{{fileName}}:{{line}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
|
@ -1,29 +0,0 @@
|
|||
{{#if categories}}
|
||||
|
||||
{{#each categories}}
|
||||
|
||||
{{#unless @first}}
|
||||
___
|
||||
{{/unless}}
|
||||
|
||||
## {{title}} {{../title}}
|
||||
|
||||
{{#each children}}
|
||||
|
||||
{{> member}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
|
||||
## {{title}}
|
||||
|
||||
{{#each children}}
|
||||
|
||||
{{> member}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
|
@ -1,35 +0,0 @@
|
|||
{{#if categories}}
|
||||
|
||||
{{#each categories}}
|
||||
|
||||
{{#unless allChildrenHaveOwnDocument}}
|
||||
|
||||
## {{title}}
|
||||
|
||||
{{#each children}}
|
||||
|
||||
{{#unless hasOwnDocument}}
|
||||
|
||||
{{> member}}
|
||||
|
||||
{{/unless}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/unless}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#each groups}}
|
||||
|
||||
{{#unless allChildrenHaveOwnDocument}}
|
||||
|
||||
{{> members.group}}
|
||||
|
||||
{{/unless}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
|
@ -1,7 +0,0 @@
|
|||
{{#ifShowPageTitle}}
|
||||
|
||||
# {{{reflectionTitle true}}}
|
||||
|
||||
{{/ifShowPageTitle}}
|
||||
|
||||
{{{reflectionPath}}}
|
|
@ -1,7 +0,0 @@
|
|||
{{> header}}
|
||||
|
||||
{{#with model.readme}}
|
||||
|
||||
{{{comment this}}}
|
||||
|
||||
{{/with}}
|
|
@ -1,101 +0,0 @@
|
|||
{{> header}}
|
||||
|
||||
{{> title}}
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{#if hasComment}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{#if model.typeParameters}}
|
||||
|
||||
## Type parameters
|
||||
|
||||
{{#with model.typeParameters}}
|
||||
|
||||
{{{typeParameterTable}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#ifShowTypeHierarchy}}
|
||||
|
||||
## Hierarchy
|
||||
|
||||
{{#with model.typeHierarchy}}
|
||||
|
||||
{{{hierarchy 0}}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/ifShowTypeHierarchy}}
|
||||
|
||||
{{#if model.implementedTypes}}
|
||||
|
||||
## Implements
|
||||
|
||||
{{#each model.implementedTypes}}
|
||||
- {{{type}}}
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if model.implementedBy}}
|
||||
|
||||
## Implemented by
|
||||
|
||||
{{#each model.implementedBy}}
|
||||
- {{{type}}}
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if model.signatures}}
|
||||
|
||||
## Callable
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{#each signatures}}
|
||||
|
||||
### {{name}}
|
||||
|
||||
{{> member.signature showSources=false }}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{> model.sources }}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if model.indexSignature}}
|
||||
|
||||
## Indexable
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{#with indexSignature}}
|
||||
|
||||
{{{indexSignatureTitle}}}
|
||||
|
||||
{{> comment}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/with}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{> main}}
|
||||
|
||||
{{/with}}
|
|
@ -1,9 +0,0 @@
|
|||
{{> header}}
|
||||
|
||||
{{> title}}
|
||||
|
||||
{{#with model}}
|
||||
|
||||
{{> member}}
|
||||
|
||||
{{/with}}
|
|
@ -1,102 +0,0 @@
|
|||
import * as fs from 'fs';
|
||||
import * as Handlebars from 'handlebars';
|
||||
import * as path from "path";
|
||||
|
||||
import {
|
||||
ContainerReflection,
|
||||
PageEvent,
|
||||
Renderer,
|
||||
DeclarationReflection,
|
||||
ReflectionKind,
|
||||
RendererEvent
|
||||
} from 'typedoc';
|
||||
import { MarkdownTheme } from 'typedoc-plugin-markdown';
|
||||
import registerTypeHelper from './type';
|
||||
|
||||
const TEMPLATE_PATH = path.join(__dirname, '..', 'profectus-theme', 'resources', 'templates');
|
||||
const PARTIALS_PATH = path.join(__dirname, '..', 'profectus-theme', 'resources', 'partials');
|
||||
|
||||
export class ProfectusTheme extends MarkdownTheme {
|
||||
constructor(renderer: Renderer) {
|
||||
super(renderer);
|
||||
|
||||
this.entryDocument = 'index.md';
|
||||
this.hideBreadcrumbs = true;
|
||||
this.hideInPageTOC = true;
|
||||
|
||||
const partialFiles = fs.readdirSync(PARTIALS_PATH);
|
||||
partialFiles.forEach((partialFile) => {
|
||||
const partialName = path.basename(partialFile, '.hbs');
|
||||
const partialContent = fs
|
||||
.readFileSync(PARTIALS_PATH + '/' + partialFile)
|
||||
.toString();
|
||||
Handlebars.registerPartial(partialName, partialContent);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("when", (context, operand_1, operator, operand_2, options) => {
|
||||
let operators = { // {{#when <operand1> 'eq' <operand2>}}
|
||||
'eq': (l,r) => l == r, // {{/when}}
|
||||
'noteq': (l,r) => l != r,
|
||||
'gt': (l,r) => (+l) > (+r), // {{#when var1 'eq' var2}}
|
||||
'gteq': (l,r) => ((+l) > (+r)) || (l == r), // eq
|
||||
'lt': (l,r) => (+l) < (+r), // {{else when var1 'gt' var2}}
|
||||
'lteq': (l,r) => ((+l) < (+r)) || (l == r), // gt
|
||||
'or': (l,r) => l || r, // {{else}}
|
||||
'and': (l,r) => l && r, // lt
|
||||
'%': (l,r) => (l % r) === 0 // {{/when}}
|
||||
}
|
||||
let result = operators[operator](operand_1,operand_2);
|
||||
if(result) return options.fn(context);
|
||||
return options.inverse(context);
|
||||
});
|
||||
}
|
||||
|
||||
getReflectionMemberTemplate() {
|
||||
const templ = super.getReflectionMemberTemplate();
|
||||
return (pageEvent) => {
|
||||
return templ(pageEvent);
|
||||
}
|
||||
}
|
||||
|
||||
getReflectionTemplate() {
|
||||
return (pageEvent) => {
|
||||
if (pageEvent.url === "index.md") {
|
||||
return "# Profectus API";
|
||||
}
|
||||
return Handlebars.compile(
|
||||
fs.readFileSync(path.join(TEMPLATE_PATH, 'reflection.hbs')).toString(),
|
||||
)(pageEvent, {
|
||||
allowProtoMethodsByDefault: true,
|
||||
allowProtoPropertiesByDefault: true,
|
||||
data: { theme: this }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getRelativeUrl(url: string) {
|
||||
const relativeUrl = super
|
||||
.getRelativeUrl(url)
|
||||
.replace(/(.*).md/, '$1')
|
||||
.replace(/ /g, '-');
|
||||
return relativeUrl.startsWith('..') ? relativeUrl : './' + relativeUrl;
|
||||
}
|
||||
|
||||
toUrl(mapping: any, reflection: DeclarationReflection) {
|
||||
let name = reflection.getFullName();
|
||||
if (name.match(/features\/.*\/.*/) != null && !name.includes("/tabs/") && !name.includes("/decorators/")) {
|
||||
name = name.replace(/features\/.*\/(.*)/, "features/$1");
|
||||
}
|
||||
return `${mapping.directory}/${name}.md`;
|
||||
}
|
||||
|
||||
get mappings() {
|
||||
return [
|
||||
{
|
||||
kind: [ReflectionKind.Module],
|
||||
isLeaf: false,
|
||||
directory: 'modules',
|
||||
template: this.getReflectionTemplate(),
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "../typedoc-theme",
|
||||
"noErrorTruncation": true,
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue