import * as React from "react";
import * as d from "../../localData";
import { listUpdateAt, neverFunc } from "../../common/util";
import { Button } from "../ui/Button";
import { ElementOperation } from "./ElementOperation";
import { Link } from "../ui/Link";
import { OneLineTextEditor } from "../ui/OneLineTextEditor";
import type { UseDefinyAppResult } from "../hook/useDefinyApp";
import { css } from "@emotion/css";
export type DataTypeOrDataTypeParameterSelection = {
readonly index: number;
readonly typeSelection: DataTypeOrDataTypeParameterSelection | undefined;
};
export type DataTypeOrDataTypeParameterValue = Pick<
UseDefinyAppResult,
"typePartResource" | "jump" | "language" | "typePartIdListInProjectResource"
> & {
readonly dataTypeOrTypeParameter: d.DataTypeOrDataTypeParameter;
readonly projectId: d.ProjectId;
readonly scopeTypePartId: d.TypePartId;
readonly onChange: (
newDataTypeOrDataTypeParameter: d.DataTypeOrDataTypeParameter
) => void;
};
const DataTypeSelectionView: ElementOperation<
DataTypeOrDataTypeParameterSelection,
DataTypeOrDataTypeParameterValue
>["selectionView"] = React.memo((props) => {
const result = getTypePartNameFromDataTypeOrDataTypeParameter(
props.value.dataTypeOrTypeParameter,
props.value.typePartResource,
props.value.scopeTypePartId
);
if (
props.value.dataTypeOrTypeParameter._ === "DataType" &&
result.type === "inProject" &&
result.typeParameterNameList.length !==
props.value.dataTypeOrTypeParameter.dataType.arguments.length
) {
return (
パラメータの個数が違う type:
{JSON.stringify(props.value.dataTypeOrTypeParameter)}, result:
{JSON.stringify(result)}
);
}
if (
result.type === "inProject" &&
props.value.dataTypeOrTypeParameter._ === "DataType"
) {
return (
{result.name}
{props.value.dataTypeOrTypeParameter.dataType.arguments.map(
(t, index) => (
)
)}
);
}
return {result.name}
;
});
DataTypeSelectionView.displayName = "DataTypeSelectionView";
type GetTypePartNameFromTypePartResult =
| {
readonly type: "inProject";
readonly name: string;
readonly typeParameterNameList: ReadonlyArray;
}
| {
readonly type: "none" | "inTypeParameter";
readonly name: string;
};
const getTypePartNameFromDataTypeOrDataTypeParameter = (
dataTypeOrDataTypeParameter: d.DataTypeOrDataTypeParameter,
typePartResource: UseDefinyAppResult["typePartResource"],
scopeTypePartId: d.TypePartId
): GetTypePartNameFromTypePartResult => {
switch (dataTypeOrDataTypeParameter._) {
case "DataType": {
return getTypePartNameFromDataTypePartId(
dataTypeOrDataTypeParameter.dataType.typePartId,
typePartResource
);
}
case "DataTypeParameter": {
const scopeTypePart =
typePartResource.getFromMemoryCache(scopeTypePartId);
if (scopeTypePart === undefined || scopeTypePart._ !== "Loaded") {
return {
type: "none",
name: "?? scopeError",
};
}
const selectedTypePart =
scopeTypePart.dataWithTime.data.dataTypeParameterList[
dataTypeOrDataTypeParameter.int32
];
if (selectedTypePart === undefined) {
return {
type: "none",
name: "?? indexError",
};
}
return {
type: "inTypeParameter",
name: selectedTypePart.name,
};
}
}
};
const getTypePartNameFromDataTypePartId = (
typePartId: d.TypePartId,
typePartResource: UseDefinyAppResult["typePartResource"]
): GetTypePartNameFromTypePartResult => {
const resource = typePartResource.getFromMemoryCache(typePartId);
if (resource === undefined) {
return { type: "none", name: ".." };
}
if (resource._ === "Unknown") {
return {
type: "none",
name: "取得に失敗した",
};
}
if (resource._ === "Deleted") {
return { type: "none", name: "存在しない" };
}
if (resource._ === "Requesting") {
return { type: "none", name: "取得中..." };
}
return {
type: "inProject",
name: resource.dataWithTime.data.name,
typeParameterNameList: resource.dataWithTime.data.dataTypeParameterList.map(
(parameter) => parameter.name
),
};
};
type TypeArgumentSelection =
| DataTypeOrDataTypeParameterSelection
| "none"
| "self";
const getTypeArgumentSelection = (
typeSelection: DataTypeOrDataTypeParameterSelection | undefined,
index: number
): TypeArgumentSelection => {
if (typeSelection === undefined || typeSelection.index !== index) {
return "none";
}
if (typeSelection.typeSelection === undefined) {
return "self";
}
return typeSelection.typeSelection;
};
const TypeArgument: React.VFC<{
index: number;
selection: TypeArgumentSelection;
name: string;
value: DataTypeOrDataTypeParameterValue;
onChangeSelection: (
typeSelection: DataTypeOrDataTypeParameterSelection
) => void;
}> = React.memo(({ index, selection, value, name, onChangeSelection }) => {
const onChangeArgumentSelection = React.useCallback(
(typeSelection: DataTypeOrDataTypeParameterSelection) => {
onChangeSelection({ index, typeSelection });
},
[index, onChangeSelection]
);
const onFocus = React.useCallback(
(event: React.FocusEvent) => {
event.preventDefault();
event.stopPropagation();
onChangeSelection({ index, typeSelection: undefined });
},
[index, onChangeSelection]
);
return (
);
});
TypeArgument.displayName = "TypeArgument";
const DataTypeDetailView: ElementOperation<
DataTypeOrDataTypeParameterSelection,
DataTypeOrDataTypeParameterValue
>["detailView"] = React.memo((props) => {
const [text, setText] = React.useState("");
React.useEffect(() => {
props.value.typePartIdListInProjectResource.requestToServerIfEmpty(
props.value.projectId
);
}, [props.value.typePartIdListInProjectResource, props.value.projectId]);
const onChange = (
dataTypeOrDataTypeParameter: d.DataTypeOrDataTypeParameter
): void => {
props.value.onChange(
setTypePartAtSelection(
props.value.dataTypeOrTypeParameter,
props.selection,
dataTypeOrDataTypeParameter
)
);
};
const onSearchTextChange = (newText: string): void => {
setText(newText);
const typePartIdListInProjectResource =
props.value.typePartIdListInProjectResource.getFromMemoryCache(
props.value.projectId
);
if (
typePartIdListInProjectResource === undefined ||
typePartIdListInProjectResource._ !== "Loaded"
) {
return;
}
const normalizedNewText = newText.trim().toLocaleLowerCase();
if (normalizedNewText.length === 0) {
return;
}
const suggestionFirst = generateTypeSuggestion(
typePartIdListInProjectResource.dataWithTime.data,
props.value.typePartResource.getFromMemoryCache,
normalizedNewText
)[0];
if (suggestionFirst === undefined) {
return;
}
onChange(
d.DataTypeOrDataTypeParameter.DataType({
typePartId: suggestionFirst.typePartId,
arguments: new Array(
suggestionFirst.typeParameterCount
).fill(
d.DataTypeOrDataTypeParameter.DataType({
arguments: [],
typePartId: d.Int32.typePartId,
})
),
})
);
};
return (
{
onChange(d.DataTypeOrDataTypeParameter.DataTypeParameter(index));
}}
/>
);
});
DataTypeDetailView.displayName = "DataTypeDetailView";
const setTypePartAtSelection = (
currentType: d.DataTypeOrDataTypeParameter,
selection: DataTypeOrDataTypeParameterSelection | undefined,
dataTypeOrDataTypeParameter: d.DataTypeOrDataTypeParameter
): d.DataTypeOrDataTypeParameter => {
if (selection === undefined || currentType._ === "DataTypeParameter") {
return dataTypeOrDataTypeParameter;
}
return setTypePartAtSelectionDataType(
currentType.dataType,
selection,
dataTypeOrDataTypeParameter
);
};
const setTypePartAtSelectionDataType = (
currentType: d.DataType,
selection: DataTypeOrDataTypeParameterSelection | undefined,
dataType: d.DataTypeOrDataTypeParameter
): d.DataTypeOrDataTypeParameter => {
if (selection === undefined) {
return dataType;
}
return d.DataTypeOrDataTypeParameter.DataType({
typePartId: currentType.typePartId,
arguments: listUpdateAt(currentType.arguments, selection.index, (t) =>
setTypePartAtSelection(t, selection.typeSelection, dataType)
),
});
};
const SelectedType: React.VFC<
Pick & {
readonly dataTypeOrDataTypeParameter: d.DataTypeOrDataTypeParameter;
readonly scopeTypePartId: d.TypePartId;
}
> = React.memo((props) => {
const result = getTypePartNameFromDataTypeOrDataTypeParameter(
props.dataTypeOrDataTypeParameter,
props.typePartResource,
props.scopeTypePartId
);
switch (props.dataTypeOrDataTypeParameter._) {
case "DataType":
return (
{result.name} を選択中
{result.name}のページ
);
case "DataTypeParameter":
return (
);
}
});
SelectedType.displayName = "SelectedType";
const SearchResult: React.VFC<
Pick & {
readonly typePartIdListInProject:
| d.ResourceState>
| undefined;
/** 前後の空白を取り除き, 小文字に変換しておく必要がある */
readonly normalizedSearchText: string;
readonly selectedDataType: d.DataType | undefined;
readonly onChange: (t: d.DataTypeOrDataTypeParameter) => void;
}
> = React.memo((props) => {
if (props.typePartIdListInProject === undefined) {
return プロジェクトに属している型パーツを取得準備中
;
}
switch (props.typePartIdListInProject._) {
case "Unknown":
return 取得に失敗した
;
case "Deleted":
return 不明なプロジェクトのため取得に失敗した
;
case "Requesting":
return 取得中
;
case "Loaded": {
const typePartList = generateTypeSuggestion(
props.typePartIdListInProject.dataWithTime.data,
props.typePartResource.getFromMemoryCache,
props.normalizedSearchText
);
return (
{typePartList.slice(0, 20).map((item) => (
{
props.onChange(
d.DataTypeOrDataTypeParameter.DataType({
typePartId: item.typePartId,
arguments: new Array(
item.typeParameterCount
).fill(
d.DataTypeOrDataTypeParameter.DataType({
arguments: [],
typePartId: d.Int32.typePartId,
})
),
})
);
}}
typeParameterCount={item.typeParameterCount}
typePartId={item.typePartId}
isSelected={
props.selectedDataType?.typePartId === item.typePartId
}
/>
))}
);
}
}
});
SearchResult.displayName = "SearchResult";
type SuggestionText = {
/** 表示する文字 */
text: string;
/** 強調表示する文字か */
isEmphasis: boolean;
};
type TypeSuggestion = {
typePartId: d.TypePartId;
name: ReadonlyArray;
typeParameterCount: number;
};
/**
* 検索文字から最適な候補を生成する
*/
const generateTypeSuggestion = (
typePartIdList: ReadonlyArray,
getFromMemoryCache: (
id_: d.TypePartId
) => d.ResourceState | undefined,
normalizedSearchText: string
): ReadonlyArray => {
const list = typePartIdList.flatMap<{
typePartId: d.TypePartId;
name: ReadonlyArray;
typeParameterCount: number;
point: number;
}>((typePartId) => {
const typePart = getFromMemoryCache(typePartId);
if (typePart === undefined || typePart._ !== "Loaded") {
return [];
}
const data = typePart.dataWithTime.data;
const includeIndex = data.name
.toLocaleLowerCase()
.indexOf(normalizedSearchText);
if (includeIndex !== -1) {
return [
{
typePartId,
name: [
{ text: data.name.slice(0, includeIndex), isEmphasis: false },
{
text: data.name.slice(
includeIndex,
includeIndex + normalizedSearchText.length
),
isEmphasis: true,
},
{
text: data.name.slice(includeIndex + normalizedSearchText.length),
isEmphasis: false,
},
{
text: ` ${new Array(data.dataTypeParameterList.length)
.fill("*")
.join(" ")}`,
isEmphasis: false,
},
],
typeParameterCount: data.dataTypeParameterList.length,
point: data.name.length - normalizedSearchText.length,
},
];
}
return [];
});
list.sort((itemA, itemB) => itemA.point - itemB.point);
return list;
};
/**
* 型を選択するボタン, ボタンの右に詳細ページへ移動するリンクがある
*/
const DataTypeItem: React.VFC<
Pick & {
typePartId: d.TypePartId;
name: ReadonlyArray;
typeParameterCount: number;
onChange: () => void;
isSelected: boolean;
}
> = React.memo(({ typePartId, name, jump, language, isSelected, onChange }) => {
return (
→
);
});
DataTypeItem.displayName = "DataTypeItem";
const DataTypeParameterList: React.VFC<
Pick & {
readonly typePartId: d.TypePartId;
readonly onChange: (t: number) => void;
readonly selectedIndex: number | undefined;
}
> = React.memo((props) => {
const typePartResource = props.typePartResource.getFromMemoryCache(
props.typePartId
);
if (typePartResource === undefined) {
return 型の情報の取得待ち
;
}
switch (typePartResource._) {
case "Deleted":
case "Unknown":
case "Requesting":
return ...
;
case "Loaded": {
const data = typePartResource.dataWithTime.data;
if (data.dataTypeParameterList.length === 0) {
return 型パラメータはない
;
}
return (
型パラメータから
{data.dataTypeParameterList.map((p, index) => (
{
props.onChange(index);
}}
isSelected={index === props.selectedIndex}
/>
))}
);
}
}
});
DataTypeParameterList.displayName = "DataTypeParameterList";
/**
* 型を選択するボタン, ボタンの右に詳細ページへ移動するリンクがある
*/
const DataTypeParameterItem: React.VFC<{
readonly name: ReadonlyArray;
readonly onChange: () => void;
readonly isSelected: boolean;
}> = ({ isSelected, onChange, name }) => {
return (
);
};
export const dataTypeOrDataTypeParameterOperation: ElementOperation<
DataTypeOrDataTypeParameterSelection,
DataTypeOrDataTypeParameterValue
> = {
moveDown: neverFunc,
moveUp: neverFunc,
moveFirstChild: () => undefined,
moveParent: () => undefined,
selectionView: DataTypeSelectionView,
detailView: DataTypeDetailView,
};