Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 2 additions & 27 deletions src/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { CoverGrid } from "@/components/cover/CoverGrid";
import { CreatePanel } from "@/components/cover/CreatePanel";
import { PromptModal } from "@/components/ui/PromptModal";
import { pickBackup } from "@/lib/backup";
import { notebookDisplayName } from "@/lib/model";
import { colors, fonts } from "@/lib/theme";
import type { NotebookType } from "@/lib/types";
import { useImportBackup } from "@/state/backup";
import { useNotebooks } from "@/state/notebooks";
import { useUi } from "@/state/ui";

export default function CoverScreen() {
const router = useRouter();
Expand All @@ -22,8 +21,6 @@ export default function CoverScreen() {
const deleteNotebook = useNotebooks((s) => s.deleteNotebook);
const renameNotebook = useNotebooks((s) => s.renameNotebook);
const recheckStorage = useNotebooks((s) => s.recheckStorage);
const importState = useNotebooks((s) => s.importState);
const showToast = useUi((s) => s.showToast);

const [creating, setCreating] = useState(false);
const [renameId, setRenameId] = useState<string | null>(null);
Expand All @@ -43,29 +40,7 @@ export default function CoverScreen() {
]);
};

const onImport = async () => {
const res = await pickBackup();
if (res.status === "canceled") return;
if (res.status === "invalid") {
showToast("⚠️ 読み込みに失敗しました…ファイルを確認してね");
return;
}
Alert.alert(
"バックアップを読み込む",
"現在のすべての手帳を、このバックアップの内容で上書きします。よろしいですか?",
[
{ text: "やめる", style: "cancel" },
{
text: "上書き",
style: "destructive",
onPress: () => {
importState(res.state);
showToast("バックアップを読み込みました🌸");
},
},
],
);
};
const onImport = useImportBackup();

const renameTarget = notebooks.find((n) => n.id === renameId);

Expand Down
30 changes: 4 additions & 26 deletions src/app/notebook/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import { StickerTray } from "@/components/board/StickerTray";
import { FlowchartSection } from "@/components/flowchart/FlowchartSection";
import { FreeformCanvas } from "@/components/freeform/FreeformCanvas";
import { NotebookSection } from "@/components/notebook/NotebookSection";
import { exportBackup, pickBackup } from "@/lib/backup";
import { exportBackup } from "@/lib/backup";
import { captureAndShare } from "@/lib/capture";
import { pickPhotoAsDataUrl } from "@/lib/files";
import { colors, fonts, TRAY_BASE_HEIGHT } from "@/lib/theme";
import type { StickerType } from "@/lib/types";
import { useImportBackup } from "@/state/backup";
import { selectCurrentNotebook, selectCurrentPage, useNotebooks } from "@/state/notebooks";
import { useUi } from "@/state/ui";

Expand Down Expand Up @@ -46,7 +47,6 @@ export default function NotebookEditor() {
const addShape = useNotebooks((s) => s.addShape);
const addPhotoTo = useNotebooks((s) => s.addPhotoTo);
const undo = useNotebooks((s) => s.undo);
const importState = useNotebooks((s) => s.importState);

const connectMode = useUi((s) => s.connectMode);
const select = useUi((s) => s.select);
Expand All @@ -65,6 +65,8 @@ export default function NotebookEditor() {
useUi.getState().resetBoardUi();
}, [page?.id]);

const onImport = useImportBackup(() => router.replace("/"));

const goBack = () => {
if (router.canGoBack()) router.back();
else router.replace("/");
Expand Down Expand Up @@ -136,30 +138,6 @@ export default function NotebookEditor() {
showToast("書き出しに失敗しました…");
}
};
const onImport = async () => {
const res = await pickBackup();
if (res.status === "canceled") return;
if (res.status === "invalid") {
showToast("⚠️ 読み込みに失敗しました…ファイルを確認してね");
return;
}
Alert.alert(
"バックアップを読み込む",
"現在のすべての手帳を、このバックアップの内容で上書きします。よろしいですか?",
[
{ text: "やめる", style: "cancel" },
{
text: "上書き",
style: "destructive",
onPress: () => {
importState(res.state);
showToast("バックアップを読み込みました🌸");
router.replace("/");
},
},
],
);
};
const onReset = () => {
Alert.alert("ページを消去", "このページの中身を全部消します。よろしいですか?", [
{ text: "やめる", style: "cancel" },
Expand Down
24 changes: 13 additions & 11 deletions src/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Page,
PageType,
Photo,
Placement,
RuleStyle,
Shape,
Step,
Expand Down Expand Up @@ -215,27 +216,31 @@ function normalizeStep(raw: unknown): Step {
return { id: str(r.id) || newId(), type: "step", text: str(r.text), done: bool(r.done) };
}

function normalizeSticker(raw: unknown): Sticker {
const r = rec(raw);
// ステッカー・シェイプ・写真に共通する配置情報(id と座標・回転)を正規化する。
function normalizePlacement(r: Record<string, unknown>): Placement {
return {
id: str(r.id) || newId(),
type: oneOf<StickerType>(r.type, STICKER_TYPES, "star"),
x: clampNum(r.x, 0, 0, MAX_POSITION),
y: clampNum(r.y, 0, 0, MAX_POSITION),
rot: num(r.rot),
};
}

function normalizeSticker(raw: unknown): Sticker {
const r = rec(raw);
return {
...normalizePlacement(r),
type: oneOf<StickerType>(r.type, STICKER_TYPES, "star"),
size: clampNum(r.size, STICKER_DEFAULT_SIZE, STICKER_MIN_SIZE, STICKER_MAX_SIZE),
};
}

function normalizeShape(raw: unknown): Shape {
const r = rec(raw);
return {
id: str(r.id) || newId(),
...normalizePlacement(r),
text: str(r.text),
x: clampNum(r.x, 0, 0, MAX_POSITION),
y: clampNum(r.y, 0, 0, MAX_POSITION),
w: clampNum(r.w, SHAPE_DEFAULT_WIDTH, SHAPE_MIN_WIDTH, SHAPE_MAX_WIDTH),
rot: num(r.rot),
};
}

Expand All @@ -244,11 +249,8 @@ function normalizePhoto(raw: unknown): Photo | null {
const dataUrl = str(r.dataUrl) || str(r.image);
if (!/^data:image\//i.test(dataUrl)) return null;
return {
id: str(r.id) || newId(),
x: clampNum(r.x, 0, 0, MAX_POSITION),
y: clampNum(r.y, 0, 0, MAX_POSITION),
...normalizePlacement(r),
w: clampNum(r.w, PHOTO_DEFAULT_WIDTH, PHOTO_MIN_WIDTH, PHOTO_MAX_WIDTH),
rot: num(r.rot),
dataUrl,
};
}
Expand Down
20 changes: 8 additions & 12 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,26 @@ export type Step = NormalStep | IfStep;

export type BranchKey = "yes" | "no";

export interface Sticker {
// ボード上に配置される装飾(ステッカー・テキスト・写真)に共通する配置情報。
export interface Placement {
id: string;
type: StickerType;
x: number;
y: number;
rot: number;
}

export interface Sticker extends Placement {
type: StickerType;
size: number;
}

export interface Shape {
id: string;
export interface Shape extends Placement {
text: string;
x: number;
y: number;
w: number;
rot: number;
}

export interface Photo {
id: string;
x: number;
y: number;
export interface Photo extends Placement {
w: number;
rot: number;
dataUrl: string;
}

Expand Down
37 changes: 37 additions & 0 deletions src/state/backup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Alert } from "react-native";
import { pickBackup } from "@/lib/backup";
import { useNotebooks } from "@/state/notebooks";
import { useUi } from "@/state/ui";

// バックアップファイルを選ばせ、確認ダイアログを経て文書全体を上書き取り込みする共通フック。
// 一覧画面・編集画面のどちらからも使う。取り込み完了後に onImported があれば呼ぶ
// (編集画面では一覧へ戻るために使う)。
export function useImportBackup(onImported?: () => void): () => Promise<void> {
const importState = useNotebooks((s) => s.importState);
const showToast = useUi((s) => s.showToast);

return async () => {
const res = await pickBackup();
if (res.status === "canceled") return;
if (res.status === "invalid") {
showToast("⚠️ 読み込みに失敗しました…ファイルを確認してね");
return;
}
Alert.alert(
"バックアップを読み込む",
"現在のすべての手帳を、このバックアップの内容で上書きします。よろしいですか?",
[
{ text: "やめる", style: "cancel" },
{
text: "上書き",
style: "destructive",
onPress: () => {
importState(res.state);
showToast("バックアップを読み込みました🌸");
onImported?.();
},
},
],
);
};
}
Loading