//
//本コードは個人の学習目的で作成されたものであり、いかなる保証も行いません。
//利用はすべて自己責任でお願いします。
//
---------------------------------------------------------
↓リマインダー開発4までのファイル構成と内容。
//ファイル構成
D:\xampp8\htdocs\shv-reminder
├index.html
├main.js
├preload.js
├package.json
├package-lock.json //自動生成ファイル
├node_modules/ //自動生成フォルダ
└data/ //自動生成フォルダ
/* -------------------------------------------------------------- */
// index.html
シンプルカレンダー
予約
分前(例: 30 → 30分前に通知)
/* ---------------------------------
------------------------------- */
//main.js
import { app, BrowserWindow, ipcMain, Notification, Tray, Menu } from "electron";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// アプリ用の保存先を「shv-reminder」に固定
app.setAppUserModelId("com.shv.reminder");
app.setPath("userData", path.join(app.getPath("appData"), "shv-reminder"));
let tray = null;
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 800,
icon: path.join(__dirname, "icon.ico"),
webPreferences: {
preload: path.join(__dirname, "preload.js"),
nodeIntegration: false,
contextIsolation: true,
sandbox: false
}
});
// レンダラから保存先を問い合わせできるようにする
ipcMain.handle("get-user-data-path", () => {
return app.getPath("userData");
});
mainWindow.loadFile("index.html");
// ✅ closeイベントをフック(×を押したとき終了させない)
mainWindow.on("close", (event) => {
event.preventDefault(); // デフォルトの終了を止める
mainWindow.hide(); // ウィンドウを隠す(タスクトレイに残る)
});
tray = new Tray(path.join(__dirname, "icon.ico"));
const contextMenu = Menu.buildFromTemplate([
{ label: "開く", click: () => mainWindow.show() },
{ label: "隠す", click: () => mainWindow.hide() },
{ type: "separator" },
{
label: "設定",
submenu: [
{
label: "自動起動を有効化",
type: "checkbox",
checked: true,
click: (menuItem) => {
app.setLoginItemSettings({
openAtLogin: menuItem.checked,
path: process.execPath,
});
}
},
{
label: "通知テスト",
click: () => {
new Notification({ title: "テスト通知", body: "これはサンプル通知です。" }).show();
}
}
]
},
{ type: "separator" },
{
label: "終了",
click: () => {
tray.destroy();
app.quit();
}
}
]);
tray.setToolTip("SHV Reminder");
tray.setContextMenu(contextMenu);
}
// ✅ 通知イベント
ipcMain.on("show-reminder", (event, { title, body, meta }) => {
const notification = new Notification({ title, body });
notification.show();
// 通知が来たらアイコンを赤丸付きに変える
tray.setImage(path.join(__dirname, "icon-alert.ico"));
});
ipcMain.on("update-badge", (event, count) => {
app.setBadgeCount(count);
});
// ✅ アプリを開いたら通常アイコンに戻す
app.on("browser-window-focus", () => {
if (tray) {
tray.setImage(path.join(__dirname, "icon.ico"));
}
});
app.whenReady().then(() => {
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
/* ---------------------------------------------------------------- */
//preload.js
import { contextBridge, ipcRenderer } from "electron";
import path from "path";
import fs from "fs";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let dataDir = null;
ipcRenderer.invoke("get-user-data-path").then((userDataPath) => {
dataDir = path.join(userDataPath, "data");
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
});
contextBridge.exposeInMainWorld("electronAPI", {
// JSON 読み込み
readJSON: (filename) => {
const pathinfo = {
"package.json": () => {
const __dirname = path.dirname(__filename);
return path.join(__dirname, `${filename}`);
},
default: () => {
return path.join(dataDir, `${filename}`);
}
};
try {
const fn = pathinfo[filename] ?? pathinfo.default;
const filePath = fn();
if (!fs.existsSync(filePath)) return {};
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
} catch (err) {
return {};
}
},
updateBadge: (count) => ipcRenderer.send("update-badge", count),
// JSON 書き込み
writeJSON: (filename, data) => {
try {
const filePath = path.join(dataDir, filename);
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
} catch (err) {
}
},
// 🔔 通知を main に依頼
sendReminder: (title, body, meta) => {
ipcRenderer.send("show-reminder", { title, body, meta });
},
// 通知サポート有無
canNotify: () => ipcRenderer.invoke("can-notify"),
//タスクアイコンから予定追加処理
onOpenAddNote: (callback) => ipcRenderer.on("open-add-note", callback),
//通知先の予定を選択
onFocusNote: (callback) =>
ipcRenderer.on("focus-note", (event, data) => {
callback(data);
}),
});
contextBridge.exposeInMainWorld("appEnv", {
NODE_ENV: process.env.NODE_ENV
});
/* ---------------------------------------------------------------- */
//package.json
{
"name": "shv-reminder",
"version": "1.1.0",
"description": "Simple Reminder",
"main": "main.js",
"type": "module",
"scripts": {
"dev": "cross-env NODE_ENV=development electron .",
"start": "cross-env NODE_ENV=production electron .",
"dist": "electron-builder"
},
"keywords": [],
"author": "Studio Happyvalley",
"license": "ISC",
"devDependencies": {
"cross-env": "^10.1.0",
"electron": "^38.1.2",
"electron-builder": "^26.0.12"
},
"build": {
"appId": "com.shv.reminder",
"productName": "SHV Reminder",
"win": {
"target": "nsis",
"icon": "icon.ico"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"runAfterFinish": true
}
}
}