Създаване на електронно меню в TypeScript?

Току-що стартирах просто приложение Electron с помощта на TypeScript и се опитвам да настроя персонализираното си меню. Следвах примера в JS, но линията

menu = Menu.buildFromTemplate(template); не успява да се компилира с грешката:

main.ts(109,35): error TS2345: Argument of type '({ label: string; submenu: ({ role: string; } | { type: string; })[]; } | { role: string; submenu...' is not assignable to parameter of type 'MenuItemConstructorOptions[]'.

Сигурно пропускам нещо. Никъде не можах да намеря тип MenuItemConstructorOptions (но може да съм търсил на грешните места). Моят пълен код за main.ts:

import { app, BrowserWindow, screen, Menu } from 'electron';
import * as path from 'path';

let win, menu;

function createWindow() {
    const electronScreen = screen;
    const size = electronScreen.getPrimaryDisplay().workAreaSize;

    win = new BrowserWindow({
        x: 0,
        y: 0,
        width: size.width,
        height: size.height
    });

    // and load the index.html of the app.
    win.loadURL('file://' + __dirname + '/index.html');
    win.webContents.openDevTools();
    win.on('closed', () => {
        win = null;
    });
}

function createMenu() {
    const template = [{
            label: 'Edit',
            submenu: [
                { role: 'undo' },
                { role: 'redo' },
                { type: 'separator' },
                { role: 'cut' },
                { role: 'copy' },
                { role: 'paste' },
                { role: 'pasteandmatchstyle' },
                { role: 'delete' },
                { role: 'selectall' }
            ]
        },
        {
            label: 'View',
            submenu: [
                { role: 'reload' },
                { role: 'forcereload' },
                { role: 'toggledevtools' },
                { type: 'separator' },
                { role: 'resetzoom' },
                { role: 'zoomin' },
                { role: 'zoomout' },
                { type: 'separator' },
                { role: 'togglefullscreen' }
            ]
        },
        { role: 'window', submenu: [{ role: 'minimize' }, { role: 'close' }] },
        {
            role: 'help',
            submenu: [{
                label: 'Learn More',
                click() {                           require('electron').shell.openExternal('https://electron.atom.io');
                }
            }]
        }
    ];
    menu = Menu.buildFromTemplate(template);
    Menu.setApplicationMenu(menu);
}

try {
    app.on('ready', function() {
        createWindow();
        createMenu();
    });
    app.on('window-all-closed', () => {
        if (process.platform !== 'darwin') {
            app.quit();
        }
    });

    app.on('activate', () => {
        if (win === null) {
            createWindow();
        }
    });
} catch (e) {
    throw e;
}

person stwissel    schedule 22.08.2017    source източник


Отговори (6)


За мен беше достатъчно да задам типа на шаблона const на Electron.MenuItemConstructorOptions[].

Например:

const template: Electron.MenuItemConstructorOptions[] = [{
        label: 'Edit',
        submenu: [
            { role: 'undo' },
            { role: 'redo' },
            { type: 'separator' },
            { role: 'cut' },
            { role: 'copy' },
            { role: 'paste' },
            { role: 'pasteandmatchstyle' },
            { role: 'delete' },
            { role: 'selectall' }
        ]
    },
    {
        label: 'View',
        submenu: [
            { role: 'reload' },
            { role: 'forcereload' },
            { role: 'toggledevtools' },
            { type: 'separator' },
            { role: 'resetzoom' },
            { role: 'zoomin' },
            { role: 'zoomout' },
            { type: 'separator' },
            { role: 'togglefullscreen' }
        ]
    },
    { role: 'window', submenu: [{ role: 'minimize' }, { role: 'close' }] },
    {
        role: 'help',
        submenu: [{
            label: 'Learn More',
            click() {
                require('electron').shell.openExternal('https://electron.atom.io');
            }
        }]
    }
];
person PhoneixS    schedule 28.09.2017

За мен проблемът беше правилната капитализация на ролята:

  { role: 'forceReload' },
  { role: 'toggleDevTools' }

над

  { role: 'forcereload' },
  { role: 'toggledevtools' }

Вижте документи.

person moonwave99    schedule 23.12.2019

Не можах да получа пробата, която работи в JS в TS. MenuItemConstructorOptions е интерфейс, дефиниран във файла electron.d.ts в електронния пакет. Въпреки това намерих заобиколно решение, като дефинирах записите в менюто поотделно и ги натиснах в празен масив. Интересното е, че записите в подменюто вътре бяха приети и работеха без проблем. Това е кодът, който работи:

  function createMenu() {
     const template = [];
     // Edit Menu
     template.push({
        label: 'Edit',
        submenu: [
           { role: 'undo' },
           { role: 'redo' },
           { type: 'separator' },
           { role: 'cut' },
           { role: 'copy' },
           { role: 'paste' },
           { role: 'pasteandmatchstyle' },
           { role: 'delete' },
           { role: 'selectall' }
        ]
     });
     // View Menu
     template.push({
        label: 'View',
        submenu: [
           { role: 'reload' },
           { role: 'forcereload' },
           { role: 'toggledevtools' },
           { type: 'separator' },
           { role: 'resetzoom' },
           { role: 'zoomin' },
           { role: 'zoomout' },
           { type: 'separator' },
           { role: 'togglefullscreen' }
        ]
     });
     // Windown menu
     template.push({
        role: 'window',
        submenu: [{ role: 'minimize' }, { role: 'close' }]
     });
     // Help menu
     template.push({
        role: 'help',
        submenu: [
           {
              label: 'Learn More',
              click() {
                 require('electron').shell.openExternal('https://electron.atom.io');
              }
           }
        ]
     });

     if (process.platform === 'darwin') {
        template.unshift({
           label: app.getName(),
           submenu: [
              { role: 'about' },
              { type: 'separator' },
              { role: 'services', submenu: [] },
              { type: 'separator' },
              { role: 'hide' },
              { role: 'hideothers' },
              { role: 'unhide' },
              { type: 'separator' },
              { role: 'quit' }
           ]
        });

        // Edit menu
        template[1].submenu.push(
           { type: 'separator' },
           { label: 'Speech', submenu: [{ role: 'startspeaking' }, { role: 'stopspeaking' }] }
        );

        // Window menu
        template[3].submenu = [{ role: 'close' }, { role: 'minimize' }, { role: 'zoom' }, { type: 'separator' }, { role: 'front' }];
     }

     menu = Menu.buildFromTemplate(template);
     Menu.setApplicationMenu(menu);
  }

Надявам се това да помогне, ако някой се натъкне на същия проблем

person stwissel    schedule 23.08.2017

В интерфейса MenuItemConstructorOptions свойството на подменюто е дефинирано с тип обединение. Така че свойството трябва да бъде прехвърлено към масива MenuItemConstructorOptions, за да може операторът за натискане да бъде разпознат:

(windowMenu.submenu as MenuItemConstructorOptions[]).push(
  {
    type: 'separator',
  },
  {
    label: 'Bring All To Front',
    role: 'front'
  }
);
person Michael Bletzinger    schedule 01.10.2019

Имате грешка / правописна грешка, предполагам тук:

{ type: 'separator' },

TypeScript не може да разреши типа поради неизвестно свойство type (трябва да бъде role според другите ви въведени данни)

person smnbbrv    schedule 22.08.2017
comment
Официалната документация: github.com/electron/electron/ blob/master/docs/api/menu-item.md казва, че типът е незадължително свойство с разрешени стойности на нормален, разделител, подменю, квадратче за отметка или радио - person stwissel; 22.08.2017
comment
@stwissel това може да е вярно. Но машинописът нещо не разбира. И дефинициите на тип MenuItemConstructorOptions трябва да се проверяват тук, а не документацията. TypeScript не може да съпостави типа данни на вашия масив с дефиницията. Бихте ли публикували определението на MenuItemConstructorOptions, моля? - person smnbbrv; 22.08.2017
comment
Това е най-смешното: не можах да намеря дефиницията за MenuItemConstructorOptions никъде. Изглежда, че липсва в текстовете. Предполагам, че трябва да го създам - person stwissel; 22.08.2017
comment
Почти съм сигурен, че съществува. Най-вероятно той се създава от нулата по време на всяко изграждане (това изглежда така, ако погледнете скриптовете package.json). Както и да е, TypeScript знае за него, иначе нямаше да даде грешка is not assignable to parameter of type 'MenuItemConstructorOptions[]' - person smnbbrv; 22.08.2017

Получих същата грешка, когато базирах синтаксиса на менюто си от Menu пример на Electron, което работи добре в JavaScript, но разстройва TypeScript. За съжаление кастингът MenuItemConstructorOptions[] за template, както предложен от PhoneixS, не работи за мен.

Изглежда, че TypeScript се задейства върху трикомпонентите в примера на Electron и не може да разбере техния произтичащ тип. В моя случай добавянето на as MenuItemConstructorOptions[] след затварящите скоби, обхващащи тройните модули, накара TypeScript да осъзнае, че те все пак са валидни подменюта.

Пълният пример за Electron във валиден TypeScript:

import { app, Menu, MenuItemConstructorOptions, shell } from "electron"

const isMac = process.platform === 'darwin'

const menu = Menu.buildFromTemplate(
  [
    // { role: 'appMenu' }
    ...(isMac ? [{
      label: app.name,
      submenu: [
        { role: 'about' },
        { type: 'separator' },
        { role: 'services' },
        { type: 'separator' },
        { role: 'hide' },
        { role: 'hideothers' },
        { role: 'unhide' },
        { type: 'separator' },
        { role: 'quit' }
      ]
    }] : []) as MenuItemConstructorOptions[],
    // { role: 'fileMenu' }
    {
      label: 'File',
      submenu: [
        isMac ? { role: 'close' } : { role: 'quit' }
      ] as MenuItemConstructorOptions[]
    },
    // { role: 'editMenu' }
    {
      label: 'Edit',
      submenu: [
        { role: 'undo' },
        { role: 'redo' },
        { type: 'separator' },
        { role: 'cut' },
        { role: 'copy' },
        { role: 'paste' },
        ...(isMac ? [
          { role: 'pasteAndMatchStyle' },
          { role: 'delete' },
          { role: 'selectAll' },
          { type: 'separator' },
          {
            label: 'Speech',
            submenu: [
              { role: 'startSpeaking' },
              { role: 'stopSpeaking' }
            ]
          }
        ] : [
          { role: 'delete' },
          { type: 'separator' },
          { role: 'selectAll' }
        ]) as MenuItemConstructorOptions[]
      ]
    },
    // { role: 'viewMenu' }
    {
      label: 'View',
      submenu: [
        { role: 'reload' },
        { role: 'forceReload' },
        { role: 'toggleDevTools' },
        { type: 'separator' },
        { role: 'resetZoom' },
        { role: 'zoomIn' },
        { role: 'zoomOut' },
        { type: 'separator' },
        { role: 'togglefullscreen' }
      ]
    },
    // { role: 'windowMenu' }
    {
      label: 'Window',
      submenu: [
        { role: 'minimize' },
        { role: 'zoom' },
        ...(isMac ? [
          { type: 'separator' },
          { role: 'front' },
          { type: 'separator' },
          { role: 'window' }
        ] : [
          { role: 'close' }
        ]) as MenuItemConstructorOptions[]
      ]
    },
    {
      role: 'help',
      submenu: [
        {
          label: 'Learn More',
          click: async () => {
            await shell.openExternal('https://electronjs.org')
          }
        }
      ]
    }
  ]
)

Menu.setApplicationMenu(menu)
person jkmartindale    schedule 02.07.2021