feat: add read-only mode to editor panels and implement project map viewing functionality
Build and Release / release (push) Successful in 38s
Build and Release / release (push) Successful in 38s
This commit is contained in:
@@ -21,6 +21,7 @@ type BindingRow = {
|
||||
|
||||
type Props = {
|
||||
setLinks: React.Dispatch<React.SetStateAction<EntityWikiLinkSnapshot[]>>;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
function wikiTitle(w: WikiSnapshot): string {
|
||||
@@ -28,7 +29,7 @@ function wikiTitle(w: WikiSnapshot): string {
|
||||
return t.length ? t : "Untitled wiki";
|
||||
}
|
||||
|
||||
function EntityWikiBindingsPanel({ setLinks }: Props) {
|
||||
function EntityWikiBindingsPanel({ setLinks, readOnly }: Props) {
|
||||
const {
|
||||
entityCatalog,
|
||||
snapshotEntityRows,
|
||||
@@ -189,186 +190,189 @@ function EntityWikiBindingsPanel({ setLinks }: Props) {
|
||||
|
||||
{collapsed ? null : (
|
||||
<div style={{ marginTop: "10px", display: "grid", gap: "8px" }}>
|
||||
<div>
|
||||
<div style={{ fontSize: "12px", color: "#94a3b8", marginBottom: "6px" }}>Entity</div>
|
||||
<select
|
||||
value={activeEntityId}
|
||||
onChange={(e) => setActiveEntityId(e.target.value)}
|
||||
style={{
|
||||
width: "100%",
|
||||
border: "1px solid #1f2937",
|
||||
background: "#0b1220",
|
||||
color: "#e5e7eb",
|
||||
borderRadius: "6px",
|
||||
padding: "8px 10px",
|
||||
fontSize: "12px",
|
||||
outline: "none",
|
||||
}}
|
||||
>
|
||||
<option value="">Select entity…</option>
|
||||
{entityChoices.map((e) => (
|
||||
<option key={e.id} value={e.id}>
|
||||
{e.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{activeEntityId ? (
|
||||
<ActiveSelectionLabel
|
||||
label={activeEntityChoice?.name || activeEntityId}
|
||||
id={activeEntityId}
|
||||
isNew={Boolean(activeEntityChoice?.isNew)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={{ fontSize: "12px", color: "#94a3b8", marginBottom: "6px" }}>Wikis</div>
|
||||
<div style={{ display: "grid", gap: "8px" }}>
|
||||
<select
|
||||
value={activeWikiId}
|
||||
onChange={(e) => setActiveWikiId(e.target.value)}
|
||||
disabled={wikiChoices.length === 0}
|
||||
style={{
|
||||
width: "100%",
|
||||
border: "1px solid #1f2937",
|
||||
background: "#0b1220",
|
||||
color: "#e5e7eb",
|
||||
borderRadius: "6px",
|
||||
padding: "8px 10px",
|
||||
fontSize: "12px",
|
||||
outline: "none",
|
||||
opacity: wikiChoices.length === 0 ? 0.7 : 1,
|
||||
cursor: wikiChoices.length === 0 ? "not-allowed" : "pointer",
|
||||
}}
|
||||
>
|
||||
<option value="">
|
||||
{wikiChoices.length === 0 ? "No wikis available" : "Select wiki…"}
|
||||
</option>
|
||||
{wikiChoices.map((w) => (
|
||||
<option key={w.id} value={w.id}>
|
||||
{w.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{activeWikiChoice ? (
|
||||
<ActiveSelectionLabel
|
||||
label={activeWikiChoice.title}
|
||||
id={activeWikiChoice.id}
|
||||
isNew={Boolean(activeWikiChoice.isNew)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{wikiChoices.length === 0 ? (
|
||||
<div style={{ fontSize: "12px", color: "#94a3b8" }}>No wiki in project yet.</div>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!activeEntityId || !activeWikiId}
|
||||
onClick={() => toggle(activeWikiId)}
|
||||
{!readOnly && (
|
||||
<>
|
||||
<div>
|
||||
<div style={{ fontSize: "12px", color: "#94a3b8", marginBottom: "6px" }}>Entity</div>
|
||||
<select
|
||||
value={activeEntityId}
|
||||
onChange={(e) => setActiveEntityId(e.target.value)}
|
||||
style={{
|
||||
border: "none",
|
||||
width: "100%",
|
||||
border: "1px solid #1f2937",
|
||||
background: "#0b1220",
|
||||
color: "#e5e7eb",
|
||||
borderRadius: "6px",
|
||||
padding: "8px 10px",
|
||||
cursor: !activeEntityId || !activeWikiId ? "not-allowed" : "pointer",
|
||||
background: activeWikiLinked ? "#334155" : "#16a34a",
|
||||
color: "white",
|
||||
fontWeight: 800,
|
||||
fontSize: 12,
|
||||
opacity: !activeEntityId || !activeWikiId ? 0.65 : 1,
|
||||
fontSize: "12px",
|
||||
outline: "none",
|
||||
}}
|
||||
>
|
||||
{activeWikiLinked ? "Unlink wiki" : "Link wiki"}
|
||||
</button>
|
||||
|
||||
{activeWikiChoice ? (
|
||||
<div style={{ fontSize: 12, color: "#94a3b8", overflowWrap: "anywhere" }}>
|
||||
{activeWikiChoice.id}
|
||||
</div>
|
||||
<option value="">Select entity…</option>
|
||||
{entityChoices.map((e) => (
|
||||
<option key={e.id} value={e.id}>
|
||||
{e.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{activeEntityId ? (
|
||||
<ActiveSelectionLabel
|
||||
label={activeEntityChoice?.name || activeEntityId}
|
||||
id={activeEntityId}
|
||||
isNew={Boolean(activeEntityChoice?.isNew)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{!activeEntityId ? (
|
||||
<div style={{ fontSize: 12, color: "#94a3b8" }}>Pick an entity to see/link wikis.</div>
|
||||
) : activeLinks.size ? (
|
||||
<div style={{ display: "grid", gap: "6px", maxHeight: 250, overflowY: "auto", paddingRight: 4 }}>
|
||||
<div style={{ fontSize: 12, color: "#94a3b8" }}>Linked wikis ({activeLinks.size})</div>
|
||||
{Array.from(activeLinks).map((id) => {
|
||||
const w = wikiChoices.find((x) => x.id === id) || null;
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
style={{
|
||||
padding: "8px",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid #1f2937",
|
||||
background: "#111827",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 8,
|
||||
}}
|
||||
title={id}
|
||||
>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 6, minWidth: 0 }}>
|
||||
<span
|
||||
style={{
|
||||
color: "#e5e7eb",
|
||||
fontSize: 12,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{w?.title || "Untitled wiki"}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ color: "#94a3b8", fontSize: 11, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
{id}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggle(id)}
|
||||
style={{
|
||||
border: "none",
|
||||
background: "#0b1220",
|
||||
color: "#fecaca",
|
||||
cursor: "pointer",
|
||||
borderRadius: 6,
|
||||
padding: "6px 8px",
|
||||
fontSize: 12,
|
||||
fontWeight: 800,
|
||||
flex: "0 0 auto",
|
||||
}}
|
||||
>
|
||||
Unlink
|
||||
</button>
|
||||
<div>
|
||||
<div style={{ fontSize: "12px", color: "#94a3b8", marginBottom: "6px" }}>Wikis</div>
|
||||
<div style={{ display: "grid", gap: "8px" }}>
|
||||
<select
|
||||
value={activeWikiId}
|
||||
onChange={(e) => setActiveWikiId(e.target.value)}
|
||||
disabled={wikiChoices.length === 0}
|
||||
style={{
|
||||
width: "100%",
|
||||
border: "1px solid #1f2937",
|
||||
background: "#0b1220",
|
||||
color: "#e5e7eb",
|
||||
borderRadius: "6px",
|
||||
padding: "8px 10px",
|
||||
fontSize: "12px",
|
||||
outline: "none",
|
||||
opacity: wikiChoices.length === 0 ? 0.7 : 1,
|
||||
cursor: wikiChoices.length === 0 ? "not-allowed" : "pointer",
|
||||
}}
|
||||
>
|
||||
<option value="">
|
||||
{wikiChoices.length === 0 ? "No wikis available" : "Select wiki…"}
|
||||
</option>
|
||||
{wikiChoices.map((w) => (
|
||||
<option key={w.id} value={w.id}>
|
||||
{w.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{activeWikiChoice ? (
|
||||
<ActiveSelectionLabel
|
||||
label={activeWikiChoice.title}
|
||||
id={activeWikiChoice.id}
|
||||
isNew={Boolean(activeWikiChoice.isNew)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{wikiChoices.length === 0 ? (
|
||||
<div style={{ fontSize: "12px", color: "#94a3b8" }}>No wiki in project yet.</div>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!activeEntityId || !activeWikiId}
|
||||
onClick={() => toggle(activeWikiId)}
|
||||
style={{
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
padding: "8px 10px",
|
||||
cursor: !activeEntityId || !activeWikiId ? "not-allowed" : "pointer",
|
||||
background: activeWikiLinked ? "#334155" : "#16a34a",
|
||||
color: "white",
|
||||
fontWeight: 800,
|
||||
fontSize: 12,
|
||||
opacity: !activeEntityId || !activeWikiId ? 0.65 : 1,
|
||||
}}
|
||||
>
|
||||
{activeWikiLinked ? "Unlink wiki" : "Link wiki"}
|
||||
</button>
|
||||
|
||||
{activeWikiChoice ? (
|
||||
<div style={{ fontSize: 12, color: "#94a3b8", overflowWrap: "anywhere" }}>
|
||||
{activeWikiChoice.id}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
) : null}
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: 12, color: "#94a3b8" }}>No wiki linked yet.</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!activeEntityId ? (
|
||||
<div style={{ fontSize: 12, color: "#94a3b8" }}>Pick an entity to see/link wikis.</div>
|
||||
) : activeLinks.size ? (
|
||||
<div style={{ display: "grid", gap: "6px", maxHeight: 250, overflowY: "auto", paddingRight: 4 }}>
|
||||
<div style={{ fontSize: 12, color: "#94a3b8" }}>Linked wikis ({activeLinks.size})</div>
|
||||
{Array.from(activeLinks).map((id) => {
|
||||
const w = wikiChoices.find((x) => x.id === id) || null;
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
style={{
|
||||
padding: "8px",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid #1f2937",
|
||||
background: "#111827",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 8,
|
||||
}}
|
||||
title={id}
|
||||
>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 6, minWidth: 0 }}>
|
||||
<span
|
||||
style={{
|
||||
color: "#e5e7eb",
|
||||
fontSize: 12,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{w?.title || "Untitled wiki"}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ color: "#94a3b8", fontSize: 11, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
{id}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggle(id)}
|
||||
style={{
|
||||
border: "none",
|
||||
background: "#0b1220",
|
||||
color: "#fecaca",
|
||||
cursor: "pointer",
|
||||
borderRadius: 6,
|
||||
padding: "6px 8px",
|
||||
fontSize: 12,
|
||||
fontWeight: 800,
|
||||
flex: "0 0 auto",
|
||||
}}
|
||||
>
|
||||
Unlink
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: 12, color: "#94a3b8" }}>No wiki linked yet.</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
borderTop: "1px solid #1f2937",
|
||||
paddingTop: 8,
|
||||
display: "grid",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 12, color: "#94a3b8" }}>
|
||||
All bindings ({activeBindingRows.length})
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
borderTop: readOnly ? "none" : "1px solid #1f2937",
|
||||
paddingTop: readOnly ? 0 : 8,
|
||||
display: "grid",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 12, color: "#94a3b8" }}>
|
||||
All bindings ({activeBindingRows.length})
|
||||
</div>
|
||||
{activeBindingRows.length ? (
|
||||
<div style={{ display: "grid", gap: 6, maxHeight: 260, overflowY: "auto", paddingRight: 4 }}>
|
||||
{activeBindingRows.map((row) => (
|
||||
|
||||
@@ -32,6 +32,7 @@ type Props = {
|
||||
selectedGeometryChildIds: string[];
|
||||
onToggleBindGeometryForSelectedGeometry?: (geometryId: string, nextChecked: boolean) => void;
|
||||
onFocusGeometry?: (geometryId: string) => void;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
function GeometryBindingPanel({
|
||||
@@ -40,6 +41,7 @@ function GeometryBindingPanel({
|
||||
selectedGeometryChildIds,
|
||||
onToggleBindGeometryForSelectedGeometry,
|
||||
onFocusGeometry,
|
||||
readOnly,
|
||||
}: Props) {
|
||||
const {
|
||||
selectedFeatureIds,
|
||||
@@ -62,7 +64,7 @@ function GeometryBindingPanel({
|
||||
selectedGeometryId ??
|
||||
(selectedFeatureIds.length > 0 ? String(selectedFeatureIds[0]) : null);
|
||||
const canBindToggle =
|
||||
Boolean(effectiveSelectedGeometryId) && typeof onToggleBindGeometryForSelectedGeometry === "function";
|
||||
!readOnly && Boolean(effectiveSelectedGeometryId) && typeof onToggleBindGeometryForSelectedGeometry === "function";
|
||||
const canFocusGeometry = typeof onFocusGeometry === "function";
|
||||
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
@@ -15,6 +15,7 @@ type Props = {
|
||||
onToggleBindEntityForSelectedGeometry?: (entityId: string, nextChecked: boolean) => void;
|
||||
onRerollEntityId?: (oldId: string, nextId: string) => void;
|
||||
onDeleteEntity?: (entityId: string) => void;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
function ProjectEntityRefsPanel({
|
||||
@@ -25,6 +26,7 @@ function ProjectEntityRefsPanel({
|
||||
onToggleBindEntityForSelectedGeometry,
|
||||
onRerollEntityId,
|
||||
onDeleteEntity,
|
||||
readOnly,
|
||||
}: Props) {
|
||||
const {
|
||||
snapshotEntityRows,
|
||||
@@ -44,11 +46,12 @@ function ProjectEntityRefsPanel({
|
||||
}))
|
||||
);
|
||||
const canBindToggle =
|
||||
!readOnly &&
|
||||
Boolean(hasSelectedGeometry) &&
|
||||
Array.isArray(selectedGeometryEntityIds) &&
|
||||
typeof onToggleBindEntityForSelectedGeometry === "function";
|
||||
|
||||
const canEditEntity = typeof onUpdateEntity === "function";
|
||||
const canEditEntity = !readOnly && typeof onUpdateEntity === "function";
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [activeEntityId, setActiveEntityId] = useState<string | null>(null);
|
||||
@@ -236,7 +239,7 @@ function ProjectEntityRefsPanel({
|
||||
)}
|
||||
</button>
|
||||
) : null}
|
||||
{typeof onDeleteEntity === "function" ? (
|
||||
{!readOnly && typeof onDeleteEntity === "function" ? (
|
||||
<button
|
||||
type="button"
|
||||
title="Xóa thực thể khỏi dự án"
|
||||
@@ -420,7 +423,7 @@ function ProjectEntityRefsPanel({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{collapsed ? null : (
|
||||
{collapsed || readOnly ? null : (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -21,6 +21,7 @@ type Props = {
|
||||
onDeleteFeatures?: (ids: (string | number)[]) => void;
|
||||
onDeselectAll?: () => void;
|
||||
onRerollGeometryId?: (oldId: string | number) => void;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
function SelectedGeometryPanel({
|
||||
@@ -31,6 +32,7 @@ function SelectedGeometryPanel({
|
||||
onDeleteFeatures,
|
||||
onDeselectAll,
|
||||
onRerollGeometryId,
|
||||
readOnly,
|
||||
}: Props) {
|
||||
const {
|
||||
geometryMetaForm,
|
||||
@@ -156,41 +158,45 @@ function SelectedGeometryPanel({
|
||||
HÀNH ĐỘNG NHANH
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "6px" }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onReplayEdit?.(representativeFeature.properties.id)}
|
||||
style={{
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
padding: "8px 10px",
|
||||
cursor: "pointer",
|
||||
background: "#2563eb",
|
||||
color: "#ffffff",
|
||||
fontWeight: 700,
|
||||
fontSize: "13px",
|
||||
textAlign: "center",
|
||||
gridColumn: "span 2",
|
||||
}}
|
||||
>
|
||||
Vào Replay ({selectedFeatures.length} geo)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDeleteFeatures?.(selectedFeatures.map(f => f.properties.id))}
|
||||
style={{
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
padding: "7px 10px",
|
||||
cursor: "pointer",
|
||||
background: "#dc2626",
|
||||
color: "#ffffff",
|
||||
fontWeight: 600,
|
||||
fontSize: "12px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Xóa ({selectedFeatures.length} geo)
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onReplayEdit?.(representativeFeature.properties.id)}
|
||||
style={{
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
padding: "8px 10px",
|
||||
cursor: "pointer",
|
||||
background: "#2563eb",
|
||||
color: "#ffffff",
|
||||
fontWeight: 700,
|
||||
fontSize: "13px",
|
||||
textAlign: "center",
|
||||
gridColumn: "span 2",
|
||||
}}
|
||||
>
|
||||
Vào Replay ({selectedFeatures.length} geo)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDeleteFeatures?.(selectedFeatures.map(f => f.properties.id))}
|
||||
style={{
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
padding: "7px 10px",
|
||||
cursor: "pointer",
|
||||
background: "#dc2626",
|
||||
color: "#ffffff",
|
||||
fontWeight: 600,
|
||||
fontSize: "12px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Xóa ({selectedFeatures.length} geo)
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDeselectAll?.()}
|
||||
@@ -204,6 +210,7 @@ function SelectedGeometryPanel({
|
||||
fontWeight: 600,
|
||||
fontSize: "12px",
|
||||
textAlign: "center",
|
||||
gridColumn: readOnly ? "span 2" : undefined,
|
||||
}}
|
||||
>
|
||||
Bỏ chọn tất cả
|
||||
@@ -229,7 +236,7 @@ function SelectedGeometryPanel({
|
||||
<div style={{ color: "#94a3b8", fontSize: "11px", overflowWrap: "anywhere", minWidth: 0, flex: 1 }}>
|
||||
{isBulkMode ? `Đang chọn ${selectedFeatures.length} geometries` : `ID: ${representativeFeature.properties.id}`}
|
||||
</div>
|
||||
{canRerollGeometryId && onRerollGeometryId && (
|
||||
{!readOnly && canRerollGeometryId && onRerollGeometryId && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRerollGeometryId(representativeFeature.properties.id)}
|
||||
@@ -268,7 +275,7 @@ function SelectedGeometryPanel({
|
||||
type_key: event.target.value,
|
||||
}))
|
||||
}
|
||||
disabled={isEntitySubmitting}
|
||||
disabled={readOnly || isEntitySubmitting}
|
||||
style={entityInputStyle}
|
||||
>
|
||||
{!hasCurrentVisibleTypeOption && geometryMetaForm.type_key ? (
|
||||
@@ -307,7 +314,7 @@ function SelectedGeometryPanel({
|
||||
}))
|
||||
}
|
||||
placeholder="time_start"
|
||||
disabled={isEntitySubmitting}
|
||||
disabled={readOnly || isEntitySubmitting}
|
||||
style={entityInputStyle}
|
||||
/>
|
||||
<input
|
||||
@@ -319,18 +326,20 @@ function SelectedGeometryPanel({
|
||||
}))
|
||||
}
|
||||
placeholder="time_end"
|
||||
disabled={isEntitySubmitting}
|
||||
disabled={readOnly || isEntitySubmitting}
|
||||
style={entityInputStyle}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleApplyGeoMeta}
|
||||
disabled={isEntitySubmitting}
|
||||
style={primaryGeometryButtonStyle}
|
||||
>
|
||||
{isBulkMode ? `Apply cho ${selectedFeatures.length} geo` : "Apply"}
|
||||
</button>
|
||||
{onReplayEdit && !isBulkMode && selectedFeatures.length > 0 && (
|
||||
{!readOnly && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleApplyGeoMeta}
|
||||
disabled={isEntitySubmitting}
|
||||
style={primaryGeometryButtonStyle}
|
||||
>
|
||||
{isBulkMode ? `Apply cho ${selectedFeatures.length} geo` : "Apply"}
|
||||
</button>
|
||||
)}
|
||||
{!readOnly && onReplayEdit && !isBulkMode && selectedFeatures.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onReplayEdit(selectedFeatures[0].properties.id)}
|
||||
@@ -359,7 +368,7 @@ function SelectedGeometryPanel({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{changeCount > 0 ? (
|
||||
{!readOnly && changeCount > 0 ? (
|
||||
<div style={{ color: "#fca5a5", fontSize: "12px" }}>
|
||||
Thay đổi sẽ vào lịch sử khi Commit.
|
||||
</div>
|
||||
|
||||
@@ -57,6 +57,7 @@ type Props = {
|
||||
projectId: string;
|
||||
setWikis: React.Dispatch<React.SetStateAction<WikiSnapshot[]>>;
|
||||
onRemoveWiki?: (wikiId: string) => void;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
function clampTitle(title: string) {
|
||||
@@ -64,7 +65,7 @@ function clampTitle(title: string) {
|
||||
return t.length ? t.slice(0, 120) : "Untitled wiki";
|
||||
}
|
||||
|
||||
function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki }: Props) {
|
||||
function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki, readOnly }: Props) {
|
||||
const { wikis, requestedActiveId } = useEditorStore(
|
||||
useShallow((state) => ({
|
||||
wikis: state.snapshotWikis,
|
||||
@@ -672,26 +673,28 @@ function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki }: Props) {
|
||||
{isNewWiki(w) ? <NewBadge /> : null}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeWiki(w.id)}
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 6,
|
||||
border: "1px solid #334155",
|
||||
background: "#0b1220",
|
||||
cursor: "pointer",
|
||||
flex: "0 0 auto",
|
||||
}}
|
||||
title="Xóa wiki khỏi dự án"
|
||||
aria-label={`Xóa wiki ${w.id}`}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
{!readOnly && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeWiki(w.id)}
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 6,
|
||||
border: "1px solid #334155",
|
||||
background: "#0b1220",
|
||||
cursor: "pointer",
|
||||
flex: "0 0 auto",
|
||||
}}
|
||||
title="Xóa wiki khỏi dự án"
|
||||
aria-label={`Xóa wiki ${w.id}`}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -702,7 +705,7 @@ function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki }: Props) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{collapsed ? null : (
|
||||
{collapsed || readOnly ? null : (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
@@ -842,15 +845,17 @@ function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki }: Props) {
|
||||
<div className="text-sm font-mono break-all text-gray-700 dark:text-gray-200">{projectId}</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={openImportPicker}
|
||||
disabled={!activeId}
|
||||
title="Import HTML"
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
{!readOnly && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={openImportPicker}
|
||||
disabled={!activeId}
|
||||
title="Import HTML"
|
||||
>
|
||||
Import
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@@ -861,11 +866,13 @@ function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki }: Props) {
|
||||
Export {wikiDocStorageFormat.toUpperCase()}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm" className="bg-brand-500 hover:bg-brand-600 text-white" onClick={saveWiki} disabled={!activeId}>
|
||||
Save
|
||||
{readOnly ? "Close" : "Cancel"}
|
||||
</Button>
|
||||
{!readOnly && (
|
||||
<Button size="sm" className="bg-brand-500 hover:bg-brand-600 text-white" onClick={saveWiki} disabled={!activeId}>
|
||||
Save
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -880,7 +887,7 @@ function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki }: Props) {
|
||||
onChange={(e) => setWikiTitle(e.target.value)}
|
||||
className="h-11 w-full rounded-xl border border-gray-200 bg-transparent px-4 py-2.5 text-sm text-gray-800 outline-none focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-white/90 dark:focus:border-brand-800"
|
||||
placeholder="Wiki title"
|
||||
disabled={!activeId}
|
||||
disabled={readOnly || !activeId}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -890,7 +897,7 @@ function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki }: Props) {
|
||||
onChange={(e) => setWikiSlug(e.target.value)}
|
||||
className="h-11 w-full rounded-xl border border-gray-200 bg-transparent px-4 py-2.5 text-sm text-gray-800 outline-none focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-white/90 dark:focus:border-brand-800"
|
||||
placeholder="wiki-slug"
|
||||
disabled={!activeId}
|
||||
disabled={readOnly || !activeId}
|
||||
/>
|
||||
</div>
|
||||
{wikiSaveError ? (
|
||||
@@ -907,7 +914,7 @@ function WikiSidebarPanel({ projectId, setWikis, onRemoveWiki }: Props) {
|
||||
modules={quillModules}
|
||||
className="min-h-[320px] uhm-wiki-quill"
|
||||
placeholder="Nhap noi dung wiki..."
|
||||
readOnly={!activeId}
|
||||
readOnly={readOnly || !activeId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user