Skip to content

implement view pages #102

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
"author": "Michael Brenan",
"license": "MIT",
"devDependencies": {
"@codemirror/autocomplete": "^6.18.6",
"@codemirror/commands": "^6.8.1",
"@codemirror/language": "https://github.com/lishid/cm-language",
"@codemirror/search": "^6.5.10",
"@codemirror/state": "^6.0.1",
"@codemirror/view": "^6.0.1",
"@codemirror/view": "^6.36.7",
"@microsoft/api-extractor": "^7.52.7",
"@types/jest": "^27.0.1",
"@types/luxon": "^2.3.2",
Expand All @@ -45,17 +48,19 @@
"typescript": "^5.4.2"
},
"dependencies": {
"@codemirror/lang-javascript": "^6.2.3",
"@datastructures-js/queue": "^4.2.3",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@replit/codemirror-vim": "^6.3.0",
"emoji-regex": "^10.2.1",
"flatqueue": "^2.0.3",
"localforage": "1.10.0",
"luxon": "^2.4.0",
"parsimmon": "^1.18.0",
"preact": "^10.17.1",
"react-select": "^5.8.0",
"preact": "^10.26.6",
"react-select": "^5.10.1",
"sorted-btree": "^1.8.1",
"sucrase": "3.35.0",
"yaml": "^2.3.3"
Expand Down
49 changes: 47 additions & 2 deletions src/api/local-api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import { IndexQuery } from "index/types/index-query";
import { Indexable } from "index/types/indexable";
import { MarkdownPage } from "index/types/markdown";
import { App } from "obsidian";
import { useFileMetadata, useFullQuery, useIndexUpdates, useInterning, useQuery } from "ui/hooks";
import { useAsync, useFileMetadata, useFullQuery, useIndexUpdates, useInterning, useQuery } from "ui/hooks";
import * as luxon from "luxon";
import * as preact from "preact";
import * as hooks from "preact/hooks";
import { Result } from "./result";
import { Group, Stack } from "./ui/layout";
import { Embed, LineSpanEmbed } from "api/ui/embed";
import { CURRENT_FILE_CONTEXT, ErrorMessage, Lit, Markdown, ObsidianLink } from "ui/markdown";
import { APP_CONTEXT, COMPONENT_CONTEXT, CURRENT_FILE_CONTEXT, DATACORE_CONTEXT, ErrorMessage, Lit, Markdown, ObsidianLink, SETTINGS_CONTEXT } from "ui/markdown";
import { CSSProperties } from "preact/compat";
import { Literal, Literals } from "expression/literal";
import { Button, Checkbox, Icon, Slider, Switch, Textbox, VanillaSelect } from "./ui/basics";
Expand Down Expand Up @@ -188,6 +188,25 @@ export class DatacoreLocalApi {
/** Execute a textual or typed index query, returning results plus performance metadata. */
public tryFullQuery(query: string | IndexQuery): Result<SearchResult<Indexable>, string> {
return this.api.tryFullQuery(query);
}
//////////////
// Contexts //
//////////////

// export the necessary contexts to enable rendering
// datacore components outside the datacore plugin
// itself
get SETTINGS_CONTEXT(): typeof SETTINGS_CONTEXT {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I'd do it like this - it's better to expose hook functions which load this data instead (like dc.useSettings()).

return SETTINGS_CONTEXT;
}
get COMPONENT_CONTEXT(): typeof COMPONENT_CONTEXT {
return COMPONENT_CONTEXT;
}
get DATACORE_CONTEXT(): typeof DATACORE_CONTEXT {
return DATACORE_CONTEXT;
}
get APP_CONTEXT(): typeof APP_CONTEXT {
return APP_CONTEXT;
}

/////////////
Expand Down Expand Up @@ -218,6 +237,7 @@ export class DatacoreLocalApi {
* React's reference-equality-based caching.
*/
public useInterning = useInterning;
public useAsync = useAsync;

/** Memoize the input automatically and process it using a DataArray; returns a vanilla array back. */
public useArray<T, U>(input: T[] | DataArray<T>, process: (data: DataArray<T>) => DataArray<U>, deps?: any[]): U[] {
Expand Down Expand Up @@ -267,6 +287,31 @@ export class DatacoreLocalApi {
/** Horizontal flexbox container; good for putting items together in a row. */
public Group = Group;

/** A component that only renders its children if `loaded` is true, otherwise defaulting to the `fallback` prop.
* Primarily intended to be used with `useAsync`.
*/
public Suspend({
loaded: loaded,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Un-neccessary : loaded.

children,
fallback,
}: {
loaded: boolean;
children: preact.ComponentChildren;
fallback: preact.ComponentChild;
}) {
return (
<>
{loaded ? (
children
) : (
<div className="datacore-loading-boundary">
<div className="datacore-loading-content">{fallback}</div>
</div>
)}
</>
);
}

/** Renders a literal value in a pretty way that respects settings. */
public Literal = (({ value, sourcePath, inline }: { value: Literal; sourcePath?: string; inline?: boolean }) => {
const implicitSourcePath = hooks.useContext(CURRENT_FILE_CONTEXT);
Expand Down
16 changes: 16 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Datacore } from "index/datacore";
import { DateTime } from "luxon";
import { App, Plugin, PluginSettingTab, Setting } from "obsidian";
import { DEFAULT_SETTINGS, Settings } from "settings";
import { DatacoreQueryView as DatacoreJSView, VIEW_TYPE_DATACOREJS } from "ui/view-page";

/** Reactive data engine for your Obsidian.md vault. */
export default class DatacorePlugin extends Plugin {
Expand Down Expand Up @@ -47,6 +48,21 @@ export default class DatacorePlugin extends Plugin {
-100
);

// Views: DatacoreJS view.
// @ts-ignore be quiet
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can this not be typed?

this.registerView(VIEW_TYPE_DATACOREJS, (leaf) => new DatacoreJSView(leaf, this.api));

// Add a command for creating a new view page.
this.addCommand({
id: "datacore-add-view-page",
name: "Create View Page",
callback: () => {
const newLeaf = this.app.workspace.getLeaf("tab");
newLeaf.setViewState({ type: VIEW_TYPE_DATACOREJS, active: true });
this.app.workspace.setActiveLeaf(newLeaf, { focus: true });
},
});

// Register JS highlighting for codeblocks.
this.register(this.registerCodeblockHighlighting());

Expand Down
62 changes: 58 additions & 4 deletions src/typings/obsidian-ex.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,49 @@
import type { DatacorePlugin } from "main";
import type { CanvasMetadataIndex } from "index/types/json/canvas";

import { Extension } from "@codemirror/state";
import type { DatacoreApi } from "api/api";
import { CanvasMetadataIndex } from "index/types/json/canvas";
import "obsidian";
import { App } from "obsidian";
import * as hooks from "preact/hooks";

/** Provides extensions used by datacore or provider to other plugins via datacore. */
declare module "obsidian" {
interface WorkspaceLeaf {
serialize(): {
id: string;
type: "leaf";
state: {
type: string;
state: any;
};
};
tabHeaderEl: HTMLElement;
tabHeaderInnerTitleEl: HTMLElement;
}
interface View {
getState(): any;
}
interface ItemView {
titleEl: HTMLElement;
getState(): any;
}

interface InternalPlugin<T> {
id: string;
name: string;
description: string;
instance: T;
}
export interface PagePreviewPlugin {
onLinkHover: (
view: View,
hovered: HTMLElement,
hoveredPath: string,
sourcePath: string,
_unknown: unknown
) => void;
}

interface FileManager {
linkUpdaters: {
canvas: {
Expand All @@ -16,16 +55,26 @@ declare module "obsidian" {
};
};
}

interface Vault {
getConfig: (conf: string) => any;
}
interface App {
appId?: string;

plugins: {
enabledPlugins: Set<string>;
plugins: {
datacore?: DatacorePlugin;
datacore?: {
api: DatacoreApi;
};
"datacore-addon-autocomplete"?: {
readonly extensions: Extension[];
};
};
};
internalPlugins: {
getPluginById: <T>(id: string) => InternalPlugin<T>;
};

embedRegistry: {
embedByExtension: {
Expand Down Expand Up @@ -54,5 +103,10 @@ declare module "obsidian" {
declare global {
interface Window {
datacore?: DatacoreApi;
app: App;
CodeMirror: {
defineMode: (mode: string, conf: (config: any) => any) => unknown;
[key: string]: any;
};
}
}
20 changes: 20 additions & 0 deletions src/typings/select.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

declare module "react-select" {
import { RefAttributes, ReactElement, JSX } from "preact/compat";
import { StateManagerAdditionalProps } from "react-select/dist/declarations/src/useStateManager";
import { Props } from "react-select/dist/declarations/src/Select";
import Select from "react-select/dist/declarations/src/Select";
export * from "react-select/dist/declarations/src/types";
declare type StateManagedPropKeys = 'inputValue' | 'menuIsOpen' | 'onChange' | 'onInputChange' | 'onMenuClose' | 'onMenuOpen' | 'value';
declare type PublicBaseSelectProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>> = JSX.LibraryManagedAttributes<typeof Select, Props<Option, IsMulti, Group>>;
declare type SelectPropsWithOptionalStateManagedProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>> = Omit<PublicBaseSelectProps<Option, IsMulti, Group>, StateManagedPropKeys> & Partial<PublicBaseSelectProps<Option, IsMulti, Group>>;
export declare type StateManagerProps<Option = unknown, IsMulti extends boolean = boolean, Group extends GroupBase<Option> = GroupBase<Option>> = SelectPropsWithOptionalStateManagedProps<Option, IsMulti, Group> & StateManagerAdditionalProps<Option>;
declare const StateManagedSelect: <
Option = unknown,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>(
props: StateManagerProps<Option, IsMulti, Group>
) => ReactElement;
export default StateManagedSelect;
}
37 changes: 37 additions & 0 deletions src/ui/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,40 @@ export function useAsElement(element: ReactNode | Literal): ReactNode {
}
}, [element]);
}
/**
* a simple hook that leverages `useEffect` and `useState` to
* return some async data and its fulfillment status.
*
* @group Hooks
* @param loader a parameterless function that returns a promise
* @param deps optional deps to pass to useEffect
* @returns a tuple in the form of [resolvedPromise, hasResolved, hasError]
*/
export function useAsync<T>(loader: () => Promise<T>, deps: any[] = []): [T, boolean, boolean] {
const [state, set] = useState<{ value: T; done: boolean; error: boolean }>({
value: undefined!,
done: false,
error: false,
});
const callId = useRef(0);
useEffect(() => {
const cid = ++callId.current;
if (state.done) {
set((prevState) => ({ ...prevState, done: false }));
}

loader().then(
(value) => {
cid === callId.current && set({ value, done: true, error: false });

return value;
},
(error) => {
cid === callId.current && set({ value: undefined!, done: true, error: true });

return error;
}
);
}, deps);
return [state.value, state.done, state.error];
}
24 changes: 24 additions & 0 deletions src/ui/view-page.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.dc-cm-editor .cm-gutters {
flex: 0 0 auto;
background-color: transparent;
color: var(--text-faint) !important;
border-right: none !important;
margin-inline-end: var(--file-folding-offset);
font-size: var(--font-ui-smaller);
z-index: 1;
font-variant: tabular-nums;
}
.cm-typeName {
color: var(--headers);
}
.dc-cm-editor {
padding: 1em;
border-radius: 0.5em;
border: 1px solid var(--h5-color);
}
.cm-tooltip.cm-tooltip-autocomplete {
background: var(--embed-bg);
}
.dc-cm-editor img.cm-widgetBuffer[aria-hidden="true"] {
display: none;
}
Loading