Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

OSDK React 앱의 드래그앤드롭

@palantir/dnd-osdk-react는 생성 OSDK의 객체와 객체 집합을 앱 내부 또는 다른 Palantir 앱과 주고받는 React 컴포넌트와 hook을 제공합니다. Workshop, Object Explorer나 Gaia처럼 해당 유형을 지원하는 앱과 연결할 수 있습니다.

처리하는 내용

라이브러리는 객체 및 객체 집합을 직렬화하고 metadata를 처리하며, 받은 객체를 객체 집합으로 바꾸어 일관된 형태로 전달합니다. 수신 집합을 실제 OSDK 질의에 사용할 수 있도록 hydration합니다. 이 과정에는 metadata 조회 및 집합 처리를 수행할 생성 OSDK client가 필요합니다.

한 drop zone에서 지원되는 사용자 데이터와 OSDK 데이터를 함께 처리할 수 있고, 받을 객체 유형을 제한하거나 drag 상태별 스타일을 지정할 수 있습니다. 동일 입력의 불필요한 backend 호출을 줄이는 깊은 비교 memoization도 제공합니다.

작업공간에 라이브러리 추가

Code Workspaces에서 React 유형의 VS Code 작업공간을 만듭니다.

Libraries에서 @palantir/dnd-osdk-react를 찾아 추가합니다. 보이지 않으면 Palantir Support에 문의합니다. 앱 템플릿과 호환되는 버전을 사용합니다.

아래 예제는 이미 인증한 TypeScript OSDK client를 ./client에서 제공하고, @my-osdk-lib/sdk가 MyObjectType을 생성한 앱에 해당합니다. 패키지와 import 이름은 실제 생성 SDK에 맞춥니다. 가상 MyObjectType은 String 기본 키와 제목으로 표시할 name 속성을 가진다고 가정합니다.

OSDK client 설정은 TypeScript 앱 준비를 사용합니다.

Drag zone

ObjectSetDragExample.tsx:

import { MyObjectType } from "@my-osdk-lib/sdk";
import { OsdkObjectDragZone } from "@palantir/dnd-osdk-react";
import client from "./client";

const objectSet = client(MyObjectType);

export default function ObjectSetDragExample() {
  return (
    <OsdkObjectDragZone client={client} objectSets={objectSet}>
      <p>이 객체 집합을 끌어오세요.</p>
    </OsdkObjectDragZone>
  );
}

objectSet은 MyObjectType에 대한 실제 OSDK 객체 집합입니다. 사용자가 드래그하면 컴포넌트가 관련 집합 정보를 payload에 넣습니다. 기존에 조회한 객체를 사용하는 흐름도 라이브러리가 제공하며 전달 형태는 설치한 컴포넌트의 타입 계약을 따릅니다.

Drop zone

acceptedObjectTypes로 받을 유형을 지정하면 onObjectSetsDrop에 API 이름별 객체 집합 배열을 전달합니다. 아래 예는 그중 첫 MyObjectType 집합의 첫 페이지를 최대 100개까지 조회하여 보여 줍니다. 전달된 모든 집합이나 모든 페이지를 읽는 예가 아닙니다.

Blueprint의 Button, Card 및 CardList를 사용하므로 @blueprintjs/core와 스타일을 앱에 준비합니다. 다음 전역 CSS import는 앱 진입점에 한 번만 둡니다.

import "normalize.css";
import "@blueprintjs/core/lib/css/blueprint.css";

DropZoneExample.tsx:

import React from "react";
import {
  type OnOsdkObjectSetsDrop,
  type OsdkInstanceObjects,
  OsdkObjectDropZone,
} from "@palantir/dnd-osdk-react";
import { Button, Card, CardList } from "@blueprintjs/core";
import { MyObjectType } from "@my-osdk-lib/sdk";
import client from "./client";
import styles from "./DropZoneExample.module.css";

const acceptedObjectTypes = [MyObjectType];

export function MyObjectTypeDropZoneExample() {
  const [objects, setObjects] = React.useState<
    OsdkInstanceObjects<[typeof MyObjectType]>
  >([]);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);
  const requestVersion = React.useRef(0);

  React.useEffect(() => () => {
    requestVersion.current += 1;
  }, []);

  const handleDrop = React.useCallback<
    OnOsdkObjectSetsDrop<[typeof MyObjectType]>
  >(async (setsByApiName) => {
    const version = ++requestVersion.current;
    setObjects([]);
    setError(null);
    setLoading(true);
    try {
      const firstSet = setsByApiName[MyObjectType.apiName]?.[0];
      if (firstSet == null) {
        throw new Error("이 유형의 객체 집합을 받지 못했습니다.");
      }
      const page = await firstSet.fetchPage({
        $pageSize: 100,
        $includeRid: true,
      });
      if (version === requestVersion.current) setObjects(page.data);
    } catch (cause) {
      if (version === requestVersion.current) {
        setError(cause instanceof Error ? cause.message : String(cause));
      }
    } finally {
      if (version === requestVersion.current) setLoading(false);
    }
  }, []);

  const clear = React.useCallback(() => {
    requestVersion.current += 1;
    setObjects([]);
    setError(null);
    setLoading(false);
  }, []);

  return (
    <section>
      <h2>객체 집합 받기</h2>
      <OsdkObjectDropZone
        client={client}
        acceptedObjectTypes={acceptedObjectTypes}
        onObjectSetsDrop={handleDrop}
        className={styles.dropZone}
        acceptedClassName={styles.dropZoneAccepted}
      >
        <p>MyObjectType 객체 또는 객체 집합을 놓으세요.</p>
      </OsdkObjectDropZone>
      <p>첫 번째 집합의 첫 페이지를 최대 100개까지 표시합니다.</p>
      {loading && <p role="status">객체를 가져오는 중입니다.</p>}
      {error !== null && <p role="alert">{error}</p>}
      {!loading && error === null && objects.length === 0 && (
        <p>표시할 객체가 없습니다.</p>
      )}
      <Button
        onClick={clear}
        disabled={!loading && error === null && objects.length === 0}
      >
        Clear ({objects.length})
      </Button>
      <CardList bordered>
        {objects.map((object) => (
          <Card key={String(object.$primaryKey)}>
            <h4>{object.name ?? "제목 값을 사용할 수 없습니다."}</h4>
            <p>{String(object.$primaryKey)}</p>
          </Card>
        ))}
      </CardList>
    </section>
  );
}

받은 집합은 실제 client 조회에 사용하며 조회 실패는 오류로 보여 줍니다. Clear 또는 새 drop 뒤에 늦게 끝난 이전 조회가 목록을 다시 채우지 않도록 요청 세대를 구분합니다. Clear는 이 화면의 결과를 지우며 온톨로지 객체를 삭제하지 않습니다.

이 예는 선언한 name 속성을 제목으로 사용합니다. 객체 제목의 실제 속성은 해당 스키마에 맞춰야 합니다. 속성값을 받을 수 없으면 그 상태와 기본 키를 표시합니다.

DropZoneExample.module.css:

.dropZone {
  min-height: 80px;
  padding: 16px;
  border: 2px solid #738091;
  border-radius: 4px;
}
.dropZoneAccepted {
  background: rgba(24, 74, 144, 0.2);
  border: 2px dashed #8ABBFF;
}

객체 조회에는 해당 객체에 접근할 권한과 인증한 OSDK client가 필요합니다. 반대 플랫폼의 객체 표현이 필요하면 보강의 지원 조건을 확인합니다.