reduce api | version control
This commit is contained in:
@@ -9,8 +9,42 @@ type Props = {
|
||||
setMode: (mode: Mode) => void;
|
||||
entityStatus?: string | null;
|
||||
onUndo: () => void;
|
||||
onSave: () => void;
|
||||
onCommit: () => void;
|
||||
onSubmit: () => void;
|
||||
onRestoreCommit: (commitId: string) => void;
|
||||
isSaving: boolean;
|
||||
isSubmitting: boolean;
|
||||
isOpeningSection: boolean;
|
||||
sectionTitle: string;
|
||||
sectionStatus: string;
|
||||
selectedSectionId: string;
|
||||
editorUserId: string;
|
||||
sectionOptions: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
state?: {
|
||||
status?: string;
|
||||
};
|
||||
}>;
|
||||
newSectionTitle: string;
|
||||
commitTitle: string;
|
||||
commitNote: string;
|
||||
onEditorUserIdChange: (userId: string) => void;
|
||||
onSelectedSectionIdChange: (sectionId: string) => void;
|
||||
onNewSectionTitleChange: (title: string) => void;
|
||||
onCommitTitleChange: (title: string) => void;
|
||||
onCommitNoteChange: (note: string) => void;
|
||||
onOpenSection: () => void;
|
||||
onCreateSection: () => void;
|
||||
commitCount: number;
|
||||
latestCommitLabel: string | null;
|
||||
commits: Array<{
|
||||
id: string;
|
||||
commit_no: number;
|
||||
kind: string;
|
||||
created_at: string;
|
||||
title: string | null;
|
||||
}>;
|
||||
changesCount: number;
|
||||
undoStack: UndoAction[];
|
||||
createdEntities: Array<{
|
||||
@@ -31,8 +65,30 @@ export default function Editor({
|
||||
setMode,
|
||||
entityStatus,
|
||||
onUndo,
|
||||
onSave,
|
||||
onCommit,
|
||||
onSubmit,
|
||||
onRestoreCommit,
|
||||
isSaving,
|
||||
isSubmitting,
|
||||
isOpeningSection,
|
||||
sectionTitle,
|
||||
sectionStatus,
|
||||
selectedSectionId,
|
||||
editorUserId,
|
||||
sectionOptions,
|
||||
newSectionTitle,
|
||||
commitTitle,
|
||||
commitNote,
|
||||
onEditorUserIdChange,
|
||||
onSelectedSectionIdChange,
|
||||
onNewSectionTitleChange,
|
||||
onCommitTitleChange,
|
||||
onCommitNoteChange,
|
||||
onOpenSection,
|
||||
onCreateSection,
|
||||
commitCount,
|
||||
latestCommitLabel,
|
||||
commits,
|
||||
changesCount,
|
||||
undoStack,
|
||||
createdEntities,
|
||||
@@ -83,6 +139,124 @@ export default function Editor({
|
||||
}}
|
||||
>
|
||||
<h3 style={{ marginBottom: "10px" }}>Editor</h3>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "12px",
|
||||
padding: "10px",
|
||||
background: "#0b1220",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid #1f2937",
|
||||
fontSize: "12px",
|
||||
color: "#cbd5e1",
|
||||
}}
|
||||
>
|
||||
<div style={{ color: "white", fontWeight: 600 }}>{sectionTitle}</div>
|
||||
<div style={{ marginTop: "4px" }}>Status: {sectionStatus}</div>
|
||||
<div>Commits: {commitCount}</div>
|
||||
<div>{latestCommitLabel || "Chưa có commit"}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "12px",
|
||||
padding: "10px",
|
||||
background: "#0b1220",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid #1f2937",
|
||||
fontSize: "12px",
|
||||
color: "#cbd5e1",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "8px", fontWeight: 600, color: "white" }}>
|
||||
Section
|
||||
</div>
|
||||
<input
|
||||
value={editorUserId}
|
||||
onChange={(event) => onEditorUserIdChange(event.target.value)}
|
||||
placeholder="User ID"
|
||||
style={{
|
||||
width: "100%",
|
||||
marginBottom: "8px",
|
||||
padding: "7px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #334155",
|
||||
background: "#111827",
|
||||
color: "white",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
disabled={isOpeningSection}
|
||||
/>
|
||||
<select
|
||||
value={selectedSectionId}
|
||||
onChange={(event) => onSelectedSectionIdChange(event.target.value)}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "7px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #334155",
|
||||
background: "#111827",
|
||||
color: "white",
|
||||
}}
|
||||
disabled={isOpeningSection}
|
||||
>
|
||||
{sectionOptions.length === 0 ? (
|
||||
<option value="">Chưa có section</option>
|
||||
) : null}
|
||||
{sectionOptions.map((section) => (
|
||||
<option key={section.id} value={section.id}>
|
||||
{section.title} {section.state?.status ? `(${section.state.status})` : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "8px",
|
||||
padding: "8px",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
cursor: isOpeningSection || !selectedSectionId ? "not-allowed" : "pointer",
|
||||
background: isOpeningSection || !selectedSectionId ? "#555" : "#2563eb",
|
||||
color: "white",
|
||||
}}
|
||||
onClick={onOpenSection}
|
||||
disabled={isOpeningSection || !selectedSectionId}
|
||||
>
|
||||
Mở section
|
||||
</button>
|
||||
<input
|
||||
value={newSectionTitle}
|
||||
onChange={(event) => onNewSectionTitleChange(event.target.value)}
|
||||
placeholder="Tên section mới"
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "10px",
|
||||
padding: "7px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #334155",
|
||||
background: "#111827",
|
||||
color: "white",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
disabled={isOpeningSection}
|
||||
/>
|
||||
<button
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "8px",
|
||||
padding: "8px",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
cursor: isOpeningSection || !newSectionTitle.trim() ? "not-allowed" : "pointer",
|
||||
background: isOpeningSection || !newSectionTitle.trim() ? "#555" : "#0f766e",
|
||||
color: "white",
|
||||
}}
|
||||
onClick={onCreateSection}
|
||||
disabled={isOpeningSection || !newSectionTitle.trim()}
|
||||
>
|
||||
Tạo và mở section
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
style={getButtonStyle("draw")}
|
||||
@@ -168,10 +342,10 @@ export default function Editor({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div style={{ marginTop: "12px", display: "flex", gap: "8px" }}>
|
||||
<div style={{ marginTop: "12px" }}>
|
||||
<button
|
||||
style={{
|
||||
flex: 1,
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
@@ -183,22 +357,125 @@ export default function Editor({
|
||||
>
|
||||
Undo
|
||||
</button>
|
||||
<button
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "8px",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
cursor: isSaving ? "not-allowed" : "pointer",
|
||||
background: isSaving ? "#555" : "#3b82f6",
|
||||
color: "white",
|
||||
opacity: changesCount === 0 ? 0.6 : 1,
|
||||
}}
|
||||
onClick={onSave}
|
||||
disabled={isSaving || changesCount === 0}
|
||||
>
|
||||
Save ({changesCount})
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
value={commitTitle}
|
||||
onChange={(event) => onCommitTitleChange(event.target.value)}
|
||||
placeholder="Commit title"
|
||||
disabled={isSaving || isSubmitting || sectionStatus === "submitted"}
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "8px",
|
||||
padding: "7px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #334155",
|
||||
background: "#111827",
|
||||
color: "white",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
/>
|
||||
<textarea
|
||||
value={commitNote}
|
||||
onChange={(event) => onCommitNoteChange(event.target.value)}
|
||||
placeholder="Commit note"
|
||||
disabled={isSaving || isSubmitting || sectionStatus === "submitted"}
|
||||
rows={3}
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "8px",
|
||||
padding: "7px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #334155",
|
||||
background: "#111827",
|
||||
color: "white",
|
||||
boxSizing: "border-box",
|
||||
resize: "vertical",
|
||||
fontFamily: "inherit",
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "8px",
|
||||
padding: "8px",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
cursor: isSaving || isSubmitting || sectionStatus === "submitted" ? "not-allowed" : "pointer",
|
||||
background: isSaving || isSubmitting || sectionStatus === "submitted" ? "#555" : "#0f766e",
|
||||
color: "white",
|
||||
}}
|
||||
onClick={onCommit}
|
||||
disabled={isSaving || isSubmitting || sectionStatus === "submitted"}
|
||||
>
|
||||
Commit ({changesCount})
|
||||
</button>
|
||||
<button
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: "8px",
|
||||
padding: "8px",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
cursor: isSubmitting || commitCount === 0 || sectionStatus === "submitted" ? "not-allowed" : "pointer",
|
||||
background: isSubmitting || commitCount === 0 || sectionStatus === "submitted" ? "#555" : "#16a34a",
|
||||
color: "white",
|
||||
opacity: commitCount === 0 ? 0.6 : 1,
|
||||
}}
|
||||
onClick={onSubmit}
|
||||
disabled={isSubmitting || commitCount === 0 || sectionStatus === "submitted"}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: "16px",
|
||||
padding: "10px",
|
||||
background: "#0b1220",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid #1f2937",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "8px", fontWeight: 600, fontSize: "14px" }}>
|
||||
Commit history
|
||||
</div>
|
||||
{commits.length === 0 ? (
|
||||
<div style={{ color: "#64748b", fontSize: "12px" }}>
|
||||
Chưa có commit
|
||||
</div>
|
||||
) : (
|
||||
<ul style={{ listStyle: "none", margin: 0, padding: 0, fontSize: "12px" }}>
|
||||
{commits.slice(0, 8).map((commit) => (
|
||||
<li
|
||||
key={commit.id}
|
||||
style={{
|
||||
padding: "6px 0",
|
||||
borderBottom: "1px solid #1f2937",
|
||||
color: "#e2e8f0",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
#{commit.commit_no} {commit.kind}
|
||||
</div>
|
||||
<button
|
||||
style={{
|
||||
marginTop: "4px",
|
||||
padding: "4px 6px",
|
||||
borderRadius: "4px",
|
||||
border: "none",
|
||||
background: "#334155",
|
||||
color: "white",
|
||||
cursor: isSaving || isSubmitting ? "not-allowed" : "pointer",
|
||||
}}
|
||||
onClick={() => onRestoreCommit(commit.id)}
|
||||
disabled={isSaving || isSubmitting}
|
||||
>
|
||||
Restore
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -265,11 +542,11 @@ export default function Editor({
|
||||
)}
|
||||
|
||||
<div style={{ fontSize: "13px", color: "#cbd5e1", marginBottom: "6px" }}>
|
||||
Geometries mới chưa lưu ({createdGeometries.length})
|
||||
Geometries mới chưa commit ({createdGeometries.length})
|
||||
</div>
|
||||
{createdGeometries.length === 0 ? (
|
||||
<div style={{ color: "#64748b", fontSize: "12px" }}>
|
||||
Chưa có geometry mới chờ save
|
||||
Chưa có geometry mới chờ commit
|
||||
</div>
|
||||
) : (
|
||||
<ul style={{ listStyle: "none", margin: 0, padding: 0, fontSize: "12px" }}>
|
||||
|
||||
@@ -42,17 +42,9 @@ type Props = {
|
||||
entityTypeOptions: EntityTypeOption[];
|
||||
geometryMetaForm: GeometryMetaFormState;
|
||||
onGeometryMetaFormChange: (key: keyof GeometryMetaFormState, value: string) => void;
|
||||
bindingGeometrySearchQuery: string;
|
||||
onBindingGeometrySearchQueryChange: (value: string) => void;
|
||||
bindingGeometrySearchResults: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
}>;
|
||||
selectedBindingGeometryId: string | null;
|
||||
onSelectBindingGeometryId: (value: string | null) => void;
|
||||
onAddSelectedBindingGeometry: () => void;
|
||||
isEntitySubmitting: boolean;
|
||||
onCreateEntityOnly: () => void;
|
||||
onApplyGeometryMetadata: () => void;
|
||||
onApplyEntitiesForSelectedGeometry: () => void;
|
||||
changeCount: number;
|
||||
entityFormStatus: string | null;
|
||||
@@ -77,14 +69,9 @@ export default function SelectedGeometryPanel({
|
||||
entityTypeOptions,
|
||||
geometryMetaForm,
|
||||
onGeometryMetaFormChange,
|
||||
bindingGeometrySearchQuery,
|
||||
onBindingGeometrySearchQueryChange,
|
||||
bindingGeometrySearchResults,
|
||||
selectedBindingGeometryId,
|
||||
onSelectBindingGeometryId,
|
||||
onAddSelectedBindingGeometry,
|
||||
isEntitySubmitting,
|
||||
onCreateEntityOnly,
|
||||
onApplyGeometryMetadata,
|
||||
onApplyEntitiesForSelectedGeometry,
|
||||
changeCount,
|
||||
entityFormStatus,
|
||||
@@ -187,10 +174,6 @@ export default function SelectedGeometryPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ color: "#94a3b8", fontSize: "12px" }}>
|
||||
Geometry phải có ít nhất 1 entity để Save.
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
@@ -202,10 +185,10 @@ export default function SelectedGeometryPanel({
|
||||
}}
|
||||
>
|
||||
<div style={{ color: "#e2e8f0", fontWeight: 700, fontSize: "12px" }}>
|
||||
Metadata geometry (chỉ áp dụng khi bind entity)
|
||||
Thuộc tính GEO
|
||||
</div>
|
||||
<div style={{ color: "#94a3b8", fontSize: "11px" }}>
|
||||
`time_start`, `time_end`, `binding` chỉ được áp dụng khi bấm nút bind entity cho geometry.
|
||||
Các giá trị này thuộc về GEO đang chọn, không phụ thuộc entity.
|
||||
</div>
|
||||
<input
|
||||
value={geometryMetaForm.time_start}
|
||||
@@ -221,44 +204,13 @@ export default function SelectedGeometryPanel({
|
||||
disabled={isEntitySubmitting}
|
||||
style={entityInputStyle}
|
||||
/>
|
||||
<input
|
||||
value={geometryMetaForm.binding}
|
||||
onChange={(event) => onGeometryMetaFormChange("binding", event.target.value)}
|
||||
placeholder="binding ids (vd: geo-id-1, geo-id-2)"
|
||||
disabled={isEntitySubmitting}
|
||||
style={entityInputStyle}
|
||||
/>
|
||||
<input
|
||||
value={bindingGeometrySearchQuery}
|
||||
onChange={(event) =>
|
||||
onBindingGeometrySearchQueryChange(event.target.value)
|
||||
}
|
||||
placeholder="Search geometry để thêm vào binding..."
|
||||
disabled={isEntitySubmitting}
|
||||
style={entityInputStyle}
|
||||
/>
|
||||
<select
|
||||
value={selectedBindingGeometryId || ""}
|
||||
onChange={(event) =>
|
||||
onSelectBindingGeometryId(event.target.value ? event.target.value : null)
|
||||
}
|
||||
disabled={isEntitySubmitting}
|
||||
style={entityInputStyle}
|
||||
>
|
||||
<option value="">-- Chọn geometry từ kết quả search binding --</option>
|
||||
{bindingGeometrySearchResults.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onAddSelectedBindingGeometry}
|
||||
onClick={onApplyGeometryMetadata}
|
||||
disabled={isEntitySubmitting}
|
||||
style={secondaryActionButtonStyle}
|
||||
style={primaryGeometryButtonStyle}
|
||||
>
|
||||
Thêm geometry đã chọn vào binding
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -327,13 +279,13 @@ export default function SelectedGeometryPanel({
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Áp dụng danh sách entity + metadata
|
||||
Áp dụng danh sách entity
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{changeCount > 0 ? (
|
||||
<div style={{ color: "#fca5a5", fontSize: "12px" }}>
|
||||
Geometry mới sẽ lưu entity khi bấm Save.
|
||||
Thay đổi sẽ vào lịch sử khi Commit.
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -470,6 +422,16 @@ const secondaryActionButtonStyle: CSSProperties = {
|
||||
color: "#ffffff",
|
||||
};
|
||||
|
||||
const primaryGeometryButtonStyle: CSSProperties = {
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
padding: "7px 8px",
|
||||
cursor: "pointer",
|
||||
background: "#0f766e",
|
||||
color: "#ffffff",
|
||||
fontWeight: 600,
|
||||
};
|
||||
|
||||
function resolveFeatureGeometryPreset(feature: Feature): EntityGeometryPreset {
|
||||
const explicitPreset = normalizeGeometryPreset(feature.properties.geometry_preset);
|
||||
if (explicitPreset) return explicitPreset;
|
||||
|
||||
Reference in New Issue
Block a user