snapcrunch / electron-preferences

A simple, consistent interface for managing user preferences within an Electron application.
MIT License
121 stars 30 forks source link

Preferences window freezes after clicking on a button #161

Closed shad0wRoot closed 2 years ago

shad0wRoot commented 2 years ago

Describe the bug I have a button in my preferences and whenever I press it, the preferences window just freezes.

Expected behavior I expect it to not freeze, but continue working.

Screenshots

Machine (please complete the following information):

Additional context Add any other context we should know

shad0wRoot commented 2 years ago

I also want to ask if there is option for lists/arrays to have maximum size

lacymorrow commented 2 years ago

Is there a repo with reproducible steps?

lacymorrow commented 2 years ago

I have a hunch this is something simple, but my spare time is all but nil

pvrobays commented 2 years ago

Hi @MKstudio-Tech

Could you share your preferences object and (part of) the code which makes the button freeze? Once we can reproduce the issue it'll probably indeed be a simple fix.

Concerning the maximum size of a list, I don't think we have anything like that for now. Could be quite simple to add though. Please create a separate issue for this. Feel free to create a PR for it.

Thanks!

shad0wRoot commented 2 years ago

Hello @pvrobays , I created an issue for maximum list size and I will post my code here as soon as i can.

shad0wRoot commented 2 years ago

Hello @pvrobays , here is my main.js file from electron project: every button makes the preferences window freeze.

// Modules to control application life and create native browser window
const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')
const ElectronPreferences = require('electron-preferences');

const preferences = new ElectronPreferences({
    // Override default preference BrowserWindow values
    browserWindowOpts: { /* ... */ },

    // Create an optional menu bar
    //menu: Menu.buildFromTemplate(/* ... */),

    // Provide a custom CSS file, relative to your appPath.
    css: 'preference-styles.css',

    // Preference file path
  //dataStore: path.join(app.getPath("userData"), "preferences.json")
    dataStore: './preferences.json', // defaults to <userData>/preferences.json

    // Preference default values
    defaults: { 
        theme: {
            theme: 'dark'
        },
    servers: {
      servers: [],
      chosen_server_no: "0"
    }
     },

    // Preference sections visible to the UI
    sections: [
        {
            id: 'servers',
            label: 'Servers',
            icon: 'cloud-26', // See the list of available icons below
            form: {
                groups: [
                    {
                        'label': 'Servers', // optional
                        'fields': [
                            {
                                label: 'Choose a server',
                                key: 'chosen_server_no',
                                type: 'dropdown',
                addItemValidator: /^[A-Za-z | : . 0-9]+$/.toString(),
                                help: 'Choose the order number of server specified in list below',
                options: [
                  { label: 'Server 1', value: 0 },
                  { label: 'Server 2', value: 1 },
                  { label: 'Server 3', value: 2 },
                  { label: 'Server 4', value: 3 },
                  { label: 'Server 5', value: 4 }
                ]

                            },
              {
                label: 'Add or remove servers',
                key: 'servers',
                type: 'list',
                size: 5,
                style: {
                  width: '75%',
                },
                help: `Syntax: name | address:port`,
                orderable: true,
                editable: true
              },
              {
                heading: 'MAXIMUM IS 5 SERVERS!',
                content:
                  `<p>The servers after the 5th can't be selected from the selecting menu, but can be reordered later.</p>`,
                type: 'message',
              },
                            // ...
                        ]
                    },
                    // ...
                ]
            }
        },
    {
            id: 'ui',
            label: 'User Interface',
            icon: 'eye-19', // See the list of available icons below
            form: {
                groups: [
                    {
                        'label': 'UI', // optional
                        'fields': [
                            {
                label: 'Get the UI from server',
                key: 'get-ui-btn',
                type: 'button',
                buttonLabel: 'Get the UI from server',
                help: 'Download the app UI from selected server',
                hideLabel: true,
              },
              {
                label: 'Delete UI',
                key: 'del-ui-tn',
                type: 'button',
                buttonLabel: 'Delete UI',
                help: 'Delete the app UI of specified server',
                hideLabel: true,
              }
                            // ...
                        ]
                    },
                    // ...
                ]
            }
        }
        // ...
    ]
})

// Show the preferences window on demand.

function createWindow () {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      contextIsolation: false,
      nodeIntegration: true,
      sandbox: false,
      preload: path.join(__dirname, "preload.js")
    }
  })

  // and load the index.html of the app.
  mainWindow.loadFile('index.html')

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
  createWindow()
  //

// Get a value from the preferences data store
const name = preferences.value('about.name');

// Save a value within the preferences data store
//preferences.value('about.name', 'Einstein');

// Subscribing to preference changes.
preferences.on('save', (preferences) => {
  console.log(`Preferences were saved.`, JSON.stringify(preferences, null, 4));
});

// Using a button field with `channel: 'reset'`
preferences.on('click', (key) => {
  if (key === 'get-ui-btn') {
    console.log('get ui pressed');
  }
});

  app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

Thanks in advance for your help

shad0wRoot commented 2 years ago

Hello, I wrote wrong version of electron up there. The corect is 19.0.3

Thanks in advance for your help

pvrobays commented 2 years ago

Hi @MKstudio-Tech By taking a quick look in the code I noticed we do the button IPC call synchronous. IMO this should be async as we're not interested in the result of the call. I presume that'll freeze the window. When I find the time I'll try it out and get back to you.

FYI, looking at your code, you might have issues with setting contextIsolation: false. Preferences depends on context isolation, which is the most secure option to use.

shad0wRoot commented 2 years ago

Hello, when I tried setting contextIsolation: true the IPC stopped working and the preferences window still freezes on button press. In my preferences I have two buttons, one of them also has a listener in main.js. The windows freezes even if I press the button without listener specified, so I'm not sure that this is the issue. Thanks in advance for your help

pvrobays commented 2 years ago

Well contextIsolation must be enabled for the preferences window (which is done by default). Otherwise the preload scripts would fail. For security reasons I would advice you to enable it everywhere within your application. More info about can be found in the electron docs.

Meanwhile I've found the time to test this myself. The freeze is indeed an issue. Further investigation pointed to the sendButtonClick being an ipcRenderer.sendSync call. As the called method in the main process returns immediately it shouldn't be an issue. However, as the functionality of sendButtonClick doesn't expect a return result, I would suggest adapting it to an async IPC call. I'll create a PR to fix this.

pvrobays commented 2 years ago

Released in 2.7.0!