mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
public commit
This commit is contained in:
33
packages/cli/package.json
Normal file
33
packages/cli/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "bknd-cli",
|
||||
"type": "module",
|
||||
"bin": "./dist/cli/index.js",
|
||||
"version": "0.0.7",
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && mkdir -p dist/{static,cli} && cp -r ../../app/dist/static/ dist/static && cp -r ../../app/dist/cli/ dist/cli",
|
||||
"cli": "node dist/cli/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.14.0",
|
||||
"hono": "^4.6.7"
|
||||
},
|
||||
"tsup": {
|
||||
"entry": [
|
||||
"src/index.ts"
|
||||
],
|
||||
"minify": true,
|
||||
"outDir": "dist",
|
||||
"format": [
|
||||
"esm"
|
||||
],
|
||||
"platform": "neutral",
|
||||
"splitting": false,
|
||||
"loader": {
|
||||
".md": "copy"
|
||||
}
|
||||
}
|
||||
}
|
||||
175
packages/plasmic/.gitignore
vendored
Normal file
175
packages/plasmic/.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
packages/plasmic/README.md
Normal file
15
packages/plasmic/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# bknd-plasmic
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.1.4. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
243
packages/plasmic/components/Image.tsx
Normal file
243
packages/plasmic/components/Image.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
"use client";
|
||||
|
||||
import type { CodeComponentMeta } from "@plasmicapp/host";
|
||||
//import { PlasmicCanvasContext } from "@plasmicapp/loader-react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
|
||||
type PlasmicImageProps = {
|
||||
asset: {
|
||||
aspectRatio?: any;
|
||||
dataUri: string;
|
||||
name: string;
|
||||
type: string;
|
||||
uid: number;
|
||||
uuid: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
uid: number;
|
||||
};
|
||||
|
||||
type ImageProps = {
|
||||
className?: string;
|
||||
src?: string | PlasmicImageProps;
|
||||
alt?: string;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
ratio?: number;
|
||||
backgroundColor?: string;
|
||||
forceLoad?: boolean;
|
||||
transformations?: string;
|
||||
transitionSpeed?: number;
|
||||
loadTreshold?: number;
|
||||
};
|
||||
|
||||
function numeric(value: number | string): number {
|
||||
return typeof value === "number" ? value : Number.parseFloat(value);
|
||||
}
|
||||
|
||||
function getDimensionDefaults(
|
||||
width: number | string | undefined,
|
||||
height: number | string | undefined,
|
||||
ratio: number | undefined,
|
||||
) {
|
||||
let _width = width;
|
||||
let _height = height;
|
||||
let _ratio = ratio;
|
||||
|
||||
if (_width && ratio) {
|
||||
_height = (1 / ratio) * numeric(_width);
|
||||
} else if (_height && ratio) {
|
||||
_width = ratio * numeric(_height);
|
||||
}
|
||||
|
||||
if (_width && _height && !_ratio) {
|
||||
_ratio = numeric(_width) / numeric(_height);
|
||||
}
|
||||
|
||||
return { width: _width, height: _height, ratio: _ratio };
|
||||
}
|
||||
|
||||
function getPlaceholderStyle(
|
||||
width: number | string | undefined,
|
||||
height: number | string | undefined,
|
||||
ratio: number | undefined,
|
||||
) {
|
||||
let paddingBottom = 0;
|
||||
if (width && height) {
|
||||
paddingBottom = (1 / (numeric(width) / numeric(height))) * 100;
|
||||
//paddingBottom = `${numeric(width)}px / ${numeric(height)}px * 100%}`;
|
||||
//
|
||||
} else if (ratio) {
|
||||
paddingBottom = (1 / ratio) * 100;
|
||||
}
|
||||
|
||||
return {
|
||||
paddingBottom: paddingBottom + "%",
|
||||
};
|
||||
}
|
||||
|
||||
export const Image: React.FC<ImageProps> = ({
|
||||
className,
|
||||
src,
|
||||
alt = "",
|
||||
width,
|
||||
height,
|
||||
ratio,
|
||||
backgroundColor = "rgba(225, 225, 225, 0.2)",
|
||||
forceLoad = false,
|
||||
transformations = "",
|
||||
transitionSpeed = 200,
|
||||
loadTreshold = 0.1,
|
||||
...rest
|
||||
}) => {
|
||||
const inEditor = false; // !!useContext(PlasmicCanvasContext);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [isInView, setIsInView] = useState(inEditor ?? forceLoad);
|
||||
const [transitioned, setTransitioned] = useState(forceLoad);
|
||||
const imgRef = useRef<any>(null);
|
||||
|
||||
if (src) {
|
||||
if (typeof src === "object") {
|
||||
src = src.asset.dataUri;
|
||||
}
|
||||
|
||||
if (/cloudinary/.test(src)) {
|
||||
if (transformations) {
|
||||
src = src.replace("/upload", "/upload/" + transformations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//console.log("after:src", src);
|
||||
|
||||
useEffect(() => {
|
||||
if (forceLoad) {
|
||||
setIsInView(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsInView(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: loadTreshold },
|
||||
);
|
||||
if (imgRef.current) {
|
||||
observer.observe(imgRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [forceLoad]);
|
||||
|
||||
const onLoad = () => {
|
||||
setTimeout(() => {
|
||||
setLoaded(true);
|
||||
}, 0);
|
||||
|
||||
setTimeout(() => {
|
||||
setTransitioned(true);
|
||||
}, transitionSpeed);
|
||||
};
|
||||
|
||||
const {
|
||||
width: _width,
|
||||
height: _height,
|
||||
ratio: _ratio,
|
||||
} = getDimensionDefaults(width, height, ratio);
|
||||
|
||||
const imgStyle: any = {
|
||||
objectFit: "cover",
|
||||
transition: `opacity ${transitionSpeed}ms linear`,
|
||||
position: "relative",
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
width: _width || "100%",
|
||||
height: "auto",
|
||||
//height: _height || "auto",
|
||||
//height: !transitioned ? _height || "auto" : "auto",
|
||||
opacity: forceLoad || loaded ? 1 : 0,
|
||||
};
|
||||
|
||||
const placeholderStyle: any = {
|
||||
position: "absolute",
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
backgroundColor,
|
||||
width: _width || "100%",
|
||||
height: 0,
|
||||
//height: transitioned ? "auto" : 0,
|
||||
...getPlaceholderStyle(_width, _height, _ratio),
|
||||
};
|
||||
|
||||
const wrapperStyle: any = {
|
||||
position: "relative",
|
||||
width: _width,
|
||||
...getPlaceholderStyle(_width, _height, _ratio),
|
||||
height: 0,
|
||||
margin: 0,
|
||||
lineHeight: 0,
|
||||
//height: _height,
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
};
|
||||
if (loaded) {
|
||||
wrapperStyle.height = "auto";
|
||||
wrapperStyle.paddingBottom = 0;
|
||||
}
|
||||
|
||||
if (!src) return <div className={className} style={wrapperStyle} ref={imgRef} />;
|
||||
|
||||
return (
|
||||
<div className={className} style={wrapperStyle} ref={imgRef}>
|
||||
<div style={placeholderStyle} />
|
||||
{isInView && (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
onLoad={onLoad}
|
||||
style={imgStyle}
|
||||
width={_width}
|
||||
height={_height}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ImageMeta: CodeComponentMeta<React.ComponentType<ImageProps>> = {
|
||||
name: "ImageLazy",
|
||||
importPath: import.meta.dir,
|
||||
props: {
|
||||
src: {
|
||||
type: "imageUrl",
|
||||
displayName: "Image",
|
||||
},
|
||||
alt: "string",
|
||||
width: "number",
|
||||
height: "number",
|
||||
ratio: "number",
|
||||
forceLoad: "boolean",
|
||||
transformations: "string",
|
||||
//backgroundColor: "color",
|
||||
transitionSpeed: {
|
||||
type: "number",
|
||||
helpText: "How fast image should fade in. Default is 200 (ms).",
|
||||
},
|
||||
loadTreshold: {
|
||||
type: "number",
|
||||
displayName: "Treshold",
|
||||
//defaultValue: 0.1,
|
||||
helpText:
|
||||
"Number between 0 and 1. Default is 0.1. Determines how much of the image must be in viewport before it gets loaded",
|
||||
},
|
||||
},
|
||||
};
|
||||
111
packages/plasmic/components/LazyRender.tsx
Normal file
111
packages/plasmic/components/LazyRender.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { CodeComponentMeta } from "@plasmicapp/host";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface LazyRenderProps {
|
||||
className?: string;
|
||||
forceLoad?: boolean;
|
||||
forceFallback?: boolean;
|
||||
threshold?: number;
|
||||
fallback?: React.ReactNode;
|
||||
delay?: number;
|
||||
children?: React.ReactNode;
|
||||
onBecomesVisible?: () => void;
|
||||
}
|
||||
|
||||
const DefaultFallback = () => <div style={{ height: 50 }}>asdf</div>;
|
||||
|
||||
export const LazyRender: React.FC<LazyRenderProps> = ({
|
||||
className = "",
|
||||
children,
|
||||
forceLoad = false,
|
||||
forceFallback = false,
|
||||
threshold = 0.1,
|
||||
delay = 0,
|
||||
fallback = <DefaultFallback />,
|
||||
onBecomesVisible,
|
||||
}) => {
|
||||
const [isVisible, setIsVisible] = useState(forceLoad);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
/* console.log("props", {
|
||||
delay,
|
||||
threshold,
|
||||
fallback,
|
||||
isVisible,
|
||||
forceLoad,
|
||||
forceFallback,
|
||||
children,
|
||||
}); */
|
||||
|
||||
useEffect(() => {
|
||||
if (forceLoad || forceFallback) {
|
||||
setIsVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const observerOptions: IntersectionObserverInit = {
|
||||
threshold: threshold < 1 ? threshold : 0.1,
|
||||
};
|
||||
|
||||
const observerCallback: IntersectionObserverCallback = (entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting && !isVisible) {
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
onBecomesVisible?.();
|
||||
if (ref.current) observer.unobserve(ref.current);
|
||||
}, delay);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(observerCallback, observerOptions);
|
||||
|
||||
if (ref.current) observer.observe(ref.current);
|
||||
|
||||
return () => {
|
||||
if (ref.current) observer.unobserve(ref.current);
|
||||
};
|
||||
}, [forceLoad, threshold, forceFallback, delay]);
|
||||
|
||||
return (
|
||||
<div className={className} ref={ref}>
|
||||
{isVisible && !forceFallback ? children : fallback}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const LazyRenderMeta: CodeComponentMeta<React.ComponentType<LazyRenderProps>> = {
|
||||
name: "LazyRender",
|
||||
importPath: import.meta.dir,
|
||||
props: {
|
||||
forceLoad: {
|
||||
type: "boolean",
|
||||
defaultValue: false,
|
||||
},
|
||||
forceFallback: {
|
||||
type: "boolean",
|
||||
defaultValue: false,
|
||||
},
|
||||
threshold: {
|
||||
type: "number",
|
||||
defaultValue: 0.1,
|
||||
},
|
||||
fallback: {
|
||||
type: "slot",
|
||||
//allowedComponents: ["*"],
|
||||
},
|
||||
delay: {
|
||||
type: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
onBecomesVisible: {
|
||||
type: "code",
|
||||
lang: "javascript",
|
||||
},
|
||||
|
||||
children: {
|
||||
type: "slot",
|
||||
//allowedComponents: ["*"],
|
||||
},
|
||||
},
|
||||
};
|
||||
23
packages/plasmic/components/WouterLink.tsx
Normal file
23
packages/plasmic/components/WouterLink.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { CodeComponentMeta } from "@plasmicapp/host";
|
||||
import { Link } from "wouter";
|
||||
|
||||
export function WouterLink({ href, className, children, ...props }) {
|
||||
return (
|
||||
<Link href={href ?? "#"} className={className} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export const WouterLinkMeta: CodeComponentMeta<any> = {
|
||||
name: "WouterLink",
|
||||
importPath: import.meta.dir,
|
||||
props: {
|
||||
href: {
|
||||
type: "href",
|
||||
},
|
||||
children: {
|
||||
type: "slot",
|
||||
},
|
||||
},
|
||||
};
|
||||
385
packages/plasmic/components/data/BkndData.tsx
Normal file
385
packages/plasmic/components/data/BkndData.tsx
Normal file
@@ -0,0 +1,385 @@
|
||||
import { type CodeComponentMeta, DataProvider, usePlasmicCanvasContext } from "@plasmicapp/host";
|
||||
import type { RepoQuery } from "bknd/data";
|
||||
import { useEntities, useEntity } from "bknd/ui";
|
||||
import { encodeSearch } from "bknd/utils";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { usePlasmicBkndContext } from "../../contexts/BkndContext";
|
||||
|
||||
type BkndEntitiesProps = {
|
||||
children?: React.ReactNode;
|
||||
loading?: React.ReactNode;
|
||||
error?: React.ReactNode;
|
||||
empty?: React.ReactNode;
|
||||
setControlContextData?: (ctxData: {
|
||||
entities: string[];
|
||||
fields: string[];
|
||||
references: string[];
|
||||
}) => void;
|
||||
className?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
withRefs?: string[];
|
||||
joinRefs?: string[];
|
||||
dataName?: string;
|
||||
entityId?: number;
|
||||
entity?: string;
|
||||
sortBy: string;
|
||||
sortDir: "asc" | "desc";
|
||||
where?: string;
|
||||
mode?: "fetch" | "react-query";
|
||||
noLayout?: boolean;
|
||||
preview?: boolean;
|
||||
previewSlot?: "loading" | "error" | "empty";
|
||||
};
|
||||
|
||||
const LoadingComponent = ({ loading }: { loading?: React.ReactNode }) => {
|
||||
return loading ? <>{loading}</> : <>Loading...</>;
|
||||
};
|
||||
|
||||
const ErrorComponent = ({ error }: { error?: React.ReactNode }) => {
|
||||
return error ? <>{error}</> : <>Error</>;
|
||||
};
|
||||
|
||||
const EmptyComponent = ({ empty }: { empty?: React.ReactNode }) => {
|
||||
return empty ? <>{empty}</> : <>No data</>;
|
||||
};
|
||||
|
||||
export function BkndData({
|
||||
children,
|
||||
loading,
|
||||
error,
|
||||
empty,
|
||||
entity,
|
||||
setControlContextData,
|
||||
dataName,
|
||||
limit,
|
||||
offset,
|
||||
entityId,
|
||||
where,
|
||||
withRefs,
|
||||
joinRefs,
|
||||
sortBy = "id",
|
||||
sortDir = "asc",
|
||||
mode = "fetch",
|
||||
noLayout,
|
||||
preview,
|
||||
previewSlot,
|
||||
...props
|
||||
}: BkndEntitiesProps) {
|
||||
const inEditor = !!usePlasmicCanvasContext();
|
||||
const plasmicContext = usePlasmicBkndContext();
|
||||
|
||||
if (inEditor && preview) {
|
||||
let Component: React.ReactNode;
|
||||
switch (previewSlot) {
|
||||
case "loading":
|
||||
Component = <LoadingComponent loading={loading} />;
|
||||
break;
|
||||
case "error":
|
||||
Component = <ErrorComponent error={error} />;
|
||||
break;
|
||||
case "empty":
|
||||
Component = <EmptyComponent empty={empty} />;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Component) {
|
||||
return noLayout ? Component : <div className={props.className}>{Component}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let _where: any = undefined;
|
||||
if (where) {
|
||||
if (typeof where === "string") {
|
||||
try {
|
||||
_where = JSON.parse(where);
|
||||
} catch (e) {}
|
||||
} else {
|
||||
_where = where;
|
||||
}
|
||||
}
|
||||
|
||||
const query = {
|
||||
limit: entityId ? undefined : limit,
|
||||
offset: entityId ? undefined : offset,
|
||||
where: _where,
|
||||
sort: { by: sortBy, dir: sortDir },
|
||||
with: withRefs,
|
||||
join: joinRefs
|
||||
};
|
||||
|
||||
console.log("---context", plasmicContext);
|
||||
if (plasmicContext.appConfig?.data?.entities) {
|
||||
const { entities, relations } = plasmicContext.appConfig.data;
|
||||
console.log("entities", entities);
|
||||
//setControlContextData?.({ entities, fields: ["id"] });
|
||||
|
||||
let fields: string[] = ["id"];
|
||||
let references: string[] = [];
|
||||
|
||||
if (entity && entity in entities) {
|
||||
fields = Object.keys(entities[entity].fields!);
|
||||
|
||||
if (relations) {
|
||||
const rels = Object.values(relations).filter(
|
||||
// biome-ignore lint/suspicious/noDoubleEquals: <explanation>
|
||||
(r: any) => r.source == entity
|
||||
);
|
||||
// @ts-ignore
|
||||
references = rels?.map((r) => r.config?.mappedBy ?? r.target);
|
||||
//console.log("relations", relations, references);
|
||||
}
|
||||
}
|
||||
|
||||
setControlContextData?.({ entities: Object.keys(entities), fields, references });
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
return <div>Select an entity</div>;
|
||||
}
|
||||
|
||||
const modeProps: ModeProps = {
|
||||
loading,
|
||||
error,
|
||||
empty,
|
||||
dataName: dataName ?? entity ?? "data",
|
||||
entityId,
|
||||
entity,
|
||||
query,
|
||||
children
|
||||
};
|
||||
|
||||
const Component =
|
||||
mode === "react-query" ? <ModeReactQuery {...modeProps} /> : <ModeFetch {...modeProps} />;
|
||||
return noLayout ? Component : <div className={props.className}>{Component}</div>;
|
||||
}
|
||||
|
||||
type ModeProps = {
|
||||
entity: string;
|
||||
dataName: string;
|
||||
children?: React.ReactNode;
|
||||
loading?: React.ReactNode;
|
||||
error?: React.ReactNode;
|
||||
empty?: React.ReactNode;
|
||||
entityId?: number;
|
||||
query?: Partial<RepoQuery>;
|
||||
};
|
||||
|
||||
const ModeFetch = ({
|
||||
children,
|
||||
loading,
|
||||
error,
|
||||
empty,
|
||||
dataName,
|
||||
entityId,
|
||||
entity,
|
||||
query
|
||||
}: ModeProps) => {
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [hasError, setError] = useState<string>();
|
||||
const plasmicContext = usePlasmicBkndContext();
|
||||
const basepath = "/api/data";
|
||||
const path = entityId ? `${basepath}/${entity}/${entityId}` : `${basepath}/${entity}`;
|
||||
console.log("query", path, query);
|
||||
const url = `${plasmicContext.baseUrl}${path}?${encodeSearch(query)}`;
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
const result = (await res.json()) as any;
|
||||
//console.log("result", result);
|
||||
setData(result.data);
|
||||
setLoading(false);
|
||||
setError(undefined);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setError(String(e));
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [url]);
|
||||
|
||||
console.log("--data", { name: dataName ?? entity ?? "data", data, isLoading, hasError });
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingComponent loading={loading} />;
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
return <ErrorComponent error={error} />;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return <EmptyComponent empty={empty} />;
|
||||
}
|
||||
console.log("--here1");
|
||||
|
||||
return (
|
||||
<DataProvider name={dataName ?? entity ?? "data"} data={data}>
|
||||
{children}
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const ModeReactQuery = (props: ModeProps) => {
|
||||
return props.entityId ? (
|
||||
<ModeReactQuerySingle {...props} />
|
||||
) : (
|
||||
<ModeReactQueryMultiple {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
const ModeReactQuerySingle = ({
|
||||
children,
|
||||
loading,
|
||||
error,
|
||||
dataName,
|
||||
entityId,
|
||||
empty,
|
||||
entity
|
||||
}: ModeProps) => {
|
||||
const container = useEntity(entity, entityId);
|
||||
const { isLoading, isError } = container.status.fetch;
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingComponent loading={loading} />;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <ErrorComponent error={error} />;
|
||||
}
|
||||
|
||||
if (!container.data) {
|
||||
return <EmptyComponent empty={empty} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<DataProvider name={dataName ?? entity ?? "data"} data={container.data}>
|
||||
{children}
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const ModeReactQueryMultiple = ({
|
||||
children,
|
||||
loading,
|
||||
error,
|
||||
empty,
|
||||
dataName,
|
||||
entity,
|
||||
query
|
||||
}: ModeProps) => {
|
||||
const container = useEntities(entity, query);
|
||||
const { isLoading, isError } = container.status.fetch;
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingComponent loading={loading} />;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <ErrorComponent error={error} />;
|
||||
}
|
||||
|
||||
if (!container.data || container.data.length === 0) {
|
||||
return <EmptyComponent empty={empty} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<DataProvider name={dataName ?? entity ?? "data"} data={container.data}>
|
||||
{children}
|
||||
</DataProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const BkndDataMeta: CodeComponentMeta<React.ComponentType<BkndEntitiesProps>> = {
|
||||
name: "BKND Data",
|
||||
section: "BKND",
|
||||
importPath: import.meta.dir,
|
||||
providesData: true,
|
||||
props: {
|
||||
entity: {
|
||||
type: "choice",
|
||||
options: (props, ctx) => ctx.entities
|
||||
},
|
||||
dataName: {
|
||||
type: "string"
|
||||
},
|
||||
entityId: {
|
||||
type: "number"
|
||||
},
|
||||
limit: {
|
||||
type: "number",
|
||||
defaultValue: 10,
|
||||
// @ts-ignore
|
||||
hidden: (props) => !!props.entityId,
|
||||
min: 0
|
||||
},
|
||||
offset: {
|
||||
type: "number",
|
||||
defaultValue: 0,
|
||||
// @ts-ignore
|
||||
hidden: (props) => !!props.entityId,
|
||||
min: 0
|
||||
},
|
||||
withRefs: {
|
||||
displayName: "With",
|
||||
type: "choice",
|
||||
multiSelect: true,
|
||||
options: (props, ctx) => ctx.references
|
||||
},
|
||||
joinRefs: {
|
||||
displayName: "Join",
|
||||
type: "choice",
|
||||
multiSelect: true,
|
||||
options: (props, ctx) => ctx.references
|
||||
},
|
||||
where: {
|
||||
type: "code",
|
||||
lang: "json"
|
||||
},
|
||||
sortBy: {
|
||||
type: "choice",
|
||||
options: (props, ctx) => ctx.fields
|
||||
},
|
||||
sortDir: {
|
||||
type: "choice",
|
||||
options: ["asc", "desc"],
|
||||
defaultValue: "asc"
|
||||
},
|
||||
children: {
|
||||
type: "slot"
|
||||
},
|
||||
loading: {
|
||||
type: "slot"
|
||||
},
|
||||
error: {
|
||||
type: "slot"
|
||||
},
|
||||
empty: {
|
||||
type: "slot"
|
||||
},
|
||||
mode: {
|
||||
type: "choice",
|
||||
options: ["fetch", "react-query"],
|
||||
defaultValue: "fetch",
|
||||
advanced: true
|
||||
},
|
||||
noLayout: {
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
advanced: true
|
||||
},
|
||||
preview: {
|
||||
type: "boolean",
|
||||
defaultValue: false,
|
||||
advanced: true
|
||||
},
|
||||
previewSlot: {
|
||||
type: "choice",
|
||||
options: ["loading", "error", "empty"],
|
||||
hidden: (props: any) => props.preview !== true,
|
||||
advanced: true
|
||||
}
|
||||
}
|
||||
};
|
||||
4
packages/plasmic/components/index.ts
Normal file
4
packages/plasmic/components/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { BkndData, BkndDataMeta } from "./data/BkndData";
|
||||
export { WouterLink, WouterLinkMeta } from "./WouterLink";
|
||||
export { Image, ImageMeta } from "./Image";
|
||||
export { LazyRender, LazyRenderMeta } from "./LazyRender";
|
||||
143
packages/plasmic/contexts/BkndContext.tsx
Normal file
143
packages/plasmic/contexts/BkndContext.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { DataProvider, GlobalActionsProvider, usePlasmicCanvasContext } from "@plasmicapp/host";
|
||||
import type { AppConfig } from "bknd";
|
||||
import { ClientProvider, useAuth, useBaseUrl } from "bknd/ui";
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
// Users will be able to set these props in Studio.
|
||||
interface BkndGlobalContextProps {
|
||||
// You might use this to override the auth URL to a test or local URL.
|
||||
baseUrl?: string;
|
||||
appConfig?: AppConfig;
|
||||
auth: any; // @todo: add typings
|
||||
}
|
||||
|
||||
type BkndContextProps = {
|
||||
baseUrl?: string;
|
||||
initialAuth?: any;
|
||||
};
|
||||
|
||||
const BkndContextContext = createContext<BkndGlobalContextProps>({} as any);
|
||||
|
||||
function getBaseUrlFromWindow() {
|
||||
if (typeof window === "undefined") {
|
||||
return "";
|
||||
}
|
||||
|
||||
const protocol = window.location.protocol;
|
||||
const host = window.location.host;
|
||||
|
||||
return `${protocol}//${host}`;
|
||||
}
|
||||
|
||||
// @todo: it's an issue that we need auth, so we cannot make baseurl adjustable (maybe add an option to useAuth with a specific base url?)
|
||||
export const BkndContext = ({
|
||||
children,
|
||||
baseUrl,
|
||||
initialAuth
|
||||
}: React.PropsWithChildren<BkndContextProps>) => {
|
||||
const auth = useAuth();
|
||||
const baseurl = useBaseUrl();
|
||||
|
||||
const [data, setData] = useState<BkndGlobalContextProps>({
|
||||
baseUrl: baseurl,
|
||||
/*baseUrl: (baseUrl && baseUrl.length > 0 ? baseUrl : getBaseUrlFromWindow()).replace(
|
||||
/\/+$/,
|
||||
""
|
||||
),*/
|
||||
auth: auth ?? initialAuth,
|
||||
appConfig: undefined
|
||||
});
|
||||
const inEditor = !!usePlasmicCanvasContext();
|
||||
console.log("context:user", data);
|
||||
|
||||
useEffect(() => {
|
||||
setData((prev) => ({ ...prev, auth: auth }));
|
||||
}, [auth.user]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (inEditor) {
|
||||
const res = await fetch(`${baseurl}/api/system/config`);
|
||||
const result = (await res.json()) as BkndGlobalContextProps["appConfig"];
|
||||
console.log("appconfig", result);
|
||||
setData((prev) => ({ ...prev, appConfig: result }));
|
||||
}
|
||||
})();
|
||||
}, [inEditor]);
|
||||
|
||||
const actions = useMemo(
|
||||
() => ({
|
||||
login: async (data: any) => {
|
||||
console.log("login", data);
|
||||
const result = await auth.login(data);
|
||||
console.log("login:result", result);
|
||||
if (result.res.ok && "user" in result.data) {
|
||||
//result.data.
|
||||
return result.data;
|
||||
} else {
|
||||
console.log("login failed", result);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
register: async (data: any) => {
|
||||
console.log("register", data);
|
||||
const result = await auth.register(data);
|
||||
console.log("register:result", result);
|
||||
if (result.res.ok && "user" in result.data) {
|
||||
//result.data.
|
||||
return result.data;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
logout: async () => {
|
||||
await auth.logout();
|
||||
console.log("logged out");
|
||||
return true;
|
||||
},
|
||||
setToken: auth.setToken
|
||||
}),
|
||||
[baseUrl]
|
||||
);
|
||||
|
||||
console.log("plasmic.bknd.context", data);
|
||||
return (
|
||||
<GlobalActionsProvider contextName="BkndContext" actions={actions}>
|
||||
<BkndContextContext.Provider value={data}>
|
||||
<DataProvider name="bknd" data={data}>
|
||||
{/*<ClientProvider baseUrl={data.baseUrl}>{children}</ClientProvider>*/}
|
||||
{children}
|
||||
</DataProvider>
|
||||
</BkndContextContext.Provider>
|
||||
</GlobalActionsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export function usePlasmicBkndContext() {
|
||||
const context = useContext(BkndContextContext);
|
||||
return context;
|
||||
}
|
||||
|
||||
export const BkndContextMeta = {
|
||||
name: "BkndContext",
|
||||
props: { baseUrl: { type: "string" }, initialAuth: { type: "object" } },
|
||||
providesData: true,
|
||||
globalActions: {
|
||||
login: {
|
||||
parameters: [{ name: "data", type: "object" }]
|
||||
},
|
||||
register: {
|
||||
parameters: [{ name: "data", type: "object" }]
|
||||
},
|
||||
logout: {
|
||||
parameters: []
|
||||
},
|
||||
setToken: {
|
||||
parameters: [{ name: "token", type: "string" }]
|
||||
},
|
||||
sayHi: {
|
||||
parameters: [{ name: "message", type: "string" }]
|
||||
}
|
||||
}
|
||||
};
|
||||
1
packages/plasmic/contexts/index.ts
Normal file
1
packages/plasmic/contexts/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { BkndContext, BkndContextMeta } from "./BkndContext";
|
||||
4
packages/plasmic/index.ts
Normal file
4
packages/plasmic/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { loader as loadBkndComponents, CatchAllPage, createWouterPlasmicApp } from "./loader";
|
||||
|
||||
export * from "./components";
|
||||
export * from "./contexts";
|
||||
109
packages/plasmic/loader.tsx
Normal file
109
packages/plasmic/loader.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { PlasmicCanvasHost, type registerComponent } from "@plasmicapp/host";
|
||||
import {
|
||||
type ComponentRenderData,
|
||||
PlasmicComponent,
|
||||
type PlasmicComponentLoader,
|
||||
PlasmicRootProvider
|
||||
} from "@plasmicapp/loader-react";
|
||||
import { forwardRef, useEffect, useState } from "react";
|
||||
import { Link, Route, Router, Switch } from "wouter";
|
||||
import {
|
||||
BkndData,
|
||||
BkndDataMeta,
|
||||
Image,
|
||||
ImageMeta,
|
||||
LazyRender,
|
||||
LazyRenderMeta,
|
||||
WouterLink,
|
||||
WouterLinkMeta
|
||||
} from "./components";
|
||||
import { BkndContext, BkndContextMeta } from "./contexts";
|
||||
|
||||
export function loader(PLASMIC: PlasmicComponentLoader) {
|
||||
PLASMIC.registerComponent(BkndData, BkndDataMeta);
|
||||
PLASMIC.registerComponent(WouterLink, WouterLinkMeta);
|
||||
PLASMIC.registerComponent(Image, ImageMeta);
|
||||
PLASMIC.registerComponent(LazyRender, LazyRenderMeta);
|
||||
PLASMIC.registerGlobalContext(BkndContext, BkndContextMeta as any);
|
||||
}
|
||||
|
||||
const CustomLink = forwardRef<any, any>((props, ref) => {
|
||||
//console.log("rendering custom link", props);
|
||||
//return null;
|
||||
if ("data-replace" in props) {
|
||||
return <a ref={ref} {...props} />;
|
||||
}
|
||||
//return <a ref={ref} {...props} />;
|
||||
// @ts-ignore it's because of the link
|
||||
return <Link ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
const Wrapper = ({ children }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100wh",
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<div style={{ opacity: 0.5, textTransform: "uppercase" }}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function CatchAllPage({
|
||||
PLASMIC,
|
||||
prefix = ""
|
||||
}: { PLASMIC: PlasmicComponentLoader; prefix?: string }) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [pageData, setPageData] = useState<ComponentRenderData | null>(null);
|
||||
|
||||
//const params = useParams();
|
||||
const pathname = location.pathname.replace(prefix, "");
|
||||
const path = pathname.length === 0 ? "/" : pathname;
|
||||
//console.log("path", path, params);
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
const pageData = await PLASMIC.maybeFetchComponentData(path);
|
||||
//console.log("pageData", pageData);
|
||||
setPageData(pageData);
|
||||
setLoading(false);
|
||||
}
|
||||
load();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <Wrapper>Loading ...</Wrapper>;
|
||||
}
|
||||
if (!pageData) {
|
||||
return <Wrapper>Not found</Wrapper>;
|
||||
}
|
||||
|
||||
const pageMeta = pageData.entryCompMetas[0];
|
||||
|
||||
// The page will already be cached from the `load` call above.
|
||||
return (
|
||||
<PlasmicRootProvider loader={PLASMIC} pageParams={pageMeta.params} Link={CustomLink}>
|
||||
<PlasmicComponent component={path} />
|
||||
</PlasmicRootProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function createWouterPlasmicApp(PLASMIC: PlasmicComponentLoader, prefix = "") {
|
||||
return function App() {
|
||||
return (
|
||||
<Router base={prefix}>
|
||||
<Switch>
|
||||
<Route path="/host" component={PlasmicCanvasHost as any} />
|
||||
<Route
|
||||
path="/*"
|
||||
component={() => <CatchAllPage PLASMIC={PLASMIC} prefix={prefix} />}
|
||||
/>
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
}
|
||||
48
packages/plasmic/package.json
Normal file
48
packages/plasmic/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@bknd/plasmic",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && bun tsup && bun run build:types",
|
||||
"watch": "bun tsup --watch --onSuccess 'bun run build:types'",
|
||||
"build:only": "rm -rf dist && bun tsup",
|
||||
"types": "bun tsc -p tsconfig.json --noEmit --skipLibCheck",
|
||||
"build:types": "bun tsc --emitDeclarationOnly",
|
||||
"updater": "bun x npm-check-updates -ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"wouter": "^3.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@plasmicapp/host": ">=1.0.0",
|
||||
"bknd": "workspace:*",
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"tsup": {
|
||||
"entry": ["index.ts"],
|
||||
"minify": true,
|
||||
"clean": true,
|
||||
"external": ["react", "react-dom", "@plasmicapp/host", "@plasmicapp/loader-react", "@plasmicapp/loader-core"],
|
||||
"format": ["esm"],
|
||||
"platform": "browser",
|
||||
"shims": true,
|
||||
"bundle": true,
|
||||
"metafile": true,
|
||||
"splitting": false,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": ["dist"]
|
||||
}
|
||||
22
packages/plasmic/tsconfig.json
Normal file
22
packages/plasmic/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": false,
|
||||
"verbatimModuleSyntax": true,
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
},
|
||||
"include": ["index.ts", "loader.tsx", "components", "contexts"],
|
||||
"exclude": ["@bknd/app", "@bknd/core", "dist", "node_modules", "build.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user