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 (
{name}:
); }); 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 (
{result.name} を選択中
); } }); 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, };