Изготовление сайтов в Санкт-Петербурге, Питере, СПБ. Создайте родное приложение для поиска GIF на рабочем столе с помощью NodeGui

NodeGui — это библиотека с открытым исходным кодом для создания кроссплатформенных собственных настольных приложений с помощью Node.js. Приложения NodeGui могут работать в macOS, Windows и Linux. Приложения, созданные с помощью NodeGui, написаны с использованием JavaScript, оформлены с помощью CSS и отображаются как собственные виджеты рабочего стола с использованием платформы Qt.

Вот некоторые особенности NodeGui:

нативные виджеты со встроенной поддержкой темного режима

низкое потребление процессора и памяти

стилизация с помощью CSS, включая полную поддержку макета Flexbox

полная поддержка Node.js API и доступ ко всем совместимым с Node.js модулям npm

отличная поддержка отладки с использованием Chrome DevTools

первоклассная поддержка TypeScript

NodeGui работает на платформе Qt, что делает его процессор и память более эффективными по сравнению с другими решениями на основе Chromium, такими как Electron. Это означает, что приложения, написанные с использованием NodeGui, не открывают экземпляр браузера и не отображают в нем пользовательский интерфейс. Вместо этого все виджеты отображаются изначально.

В этом руководстве показано, как установить NodeGui и использовать его для создания поисковика мемов, который находится в системном трее и взаимодействует с GIPHY API.

Полный исходный код этого руководства доступен на GitHub.

Установка и базовая настройка

В этом руководстве предполагается, что у вас установлен Node.js версии 12 или выше. Вы можете убедиться, что и Node, и npm доступны, запустив:

# This command should print the version of Node.js

node -v

# This command should print the version of npm

npm -v

Если вам нужна помощь на этом этапе, ознакомьтесь с нашим руководством по установке Node.

Установите CMake и инструменты компиляции

NodeGui требует инструментов компиляции CMake и C++ для создания собственного слоя C++ проекта. Убедитесь, что вы установили CMake ≥ 3.1 вместе с компилятором C++, который поддерживает C++11 и выше. Подробные инструкции немного отличаются в зависимости от вашей операционной системы.

macOS

Рекомендуется устанавливать CMake с помощью Homebrew. Запустите следующие команды в терминале после установки Homebrew:

brew install cmake

brew install make

Вы можете подтвердить установку, выполнив:

# This command should print the version of CMake which should be higher than 3.1

cmake —version

make —version

Наконец, вам понадобится GCC/Clang для компиляции кода C++. Убедитесь, что у вас установлен GCC, используя эту команду:

gcc —version

Если у вас не установлен GCC, обязательно установите Инструменты командной строки для Xcode или Инструменты разработчика XCode со страницы разработчика Apple.

Окна

Вы можете установить CMake в Windows, загрузив последнюю версию со страницы загрузки CMake.

Настоятельно рекомендуется использовать Powershell в качестве предпочтительного терминала в Windows.

Вы можете подтвердить установку CMake, запустив:

# This command should print the version of CMake which should be higher than 3.1

cmake —version

Наконец, вам нужен компилятор C++. Одна из возможностей — установить Visual Studio 2017 или более позднюю версию. В процессе установки рекомендуется выбрать настольную разработку с рабочей нагрузкой C++.

линукс

В этом руководстве мы сосредоточимся на Ubuntu 18.04. Рекомендуется устанавливать CMake с помощью менеджера пакетов. Выполните следующие команды в терминале:

sudo apt-get install pkg-configbuild-essential

sudo apt-get install cmake make

Вы можете подтвердить установку, выполнив:

# This command should print the version of CMake which should be higher than 3.1

cmake —version

make —version

Наконец, вам нужен GCC для компиляции кода C++. Убедитесь, что у вас установлен GCC, с помощью команды:

# gcc version should be ≥ v7

gcc —version

Привет, мир

Чтобы начать работу с нашим мем-приложением NodeGui, мы клонируем начальный проект.

Примечание. Для запуска этого требуется Git и npm.

Откройте терминал и запустите:

git clone https://github.com/nodegui/nodegui-starter memeapp

cd memeapp

npm install

npm start

Если все пойдет хорошо, вы должны увидеть на экране работающее приложение Hello World NodeGui.

Привет, мир, пример NodeGui

По умолчанию проект nodegui-starter является проектом TypeScript. Однако в этом уроке мы будем писать наше приложение на JavaScript. Чтобы преобразовать наш стартер в проект JS, мы внесем следующие небольшие изменения:

Удалите index.tsфайл в srcпапке.

Создайте новый файл index.jsв srcкаталоге со следующим содержимым:

источник/index.js

const { QMainWindow, QLabel } = require ('@nodegui/nodegui’) ;

const win = new QMainWindow () ;

win.setWindowTitle ('Meme Search’) ;

const label = new QLabel () ;

label.setText ('Hello World’) ;

win.setCentralWidget (label) ;

win.show () ;

global.win = win;

Что касается разработки, приложение NodeGui по сути является приложением Node.js. Все API и функции, имеющиеся в NodeGui, доступны через @nodegui/nodeguiмодуль, который может потребоваться, как и любой другой модуль Node.js. Кроме того, у вас есть доступ ко всем API-интерфейсам Node.js и модулям Node. NodeGui использует собственные компоненты вместо веб-компонентов в качестве строительных блоков.

В приведенном выше примере мы импортировали QMainWindow и QLabel для создания собственного окна, отображающего текст «Hello World».

Теперь снова запустите приложение:

npm start

Привет, мир JavaScript-версия

Теперь, когда у нас есть готовые базовые настройки, давайте начнем создавать наш поисковик мемов 🥳.

Примечание. Если что-то не работает при выполнении этого руководства, проверьте package.jsonфайл, чтобы убедиться, что начальный проект использует самую последнюю версию NodeGui.

Отображение анимированного GIF

Поскольку мемы, как правило, представляют собой анимированные GIF-файлы, мы начнем с создания основного окна, которое отображает изображение GIF из URL-адреса.

Для этого мы будем использовать QMovie вместе с QLabel. QMovie — это не виджет, а контейнер, который может воспроизводить простые анимации. Мы будем использовать его в сочетании с QLabel.

Пример использования QMovie выглядит так:

const movie = new QMovie () ;

movie.setFileName ('/absolute/path/to/animated.gif’) ;

movie.start () ;

const animatedLabel = new QLabel () ;

animatedLabel.setMovie (movie) ;

Поскольку мы хотим загрузить изображение с URL-адреса, мы не можем использовать метод setFileNameQMovie, который зарезервирован только для локальных файлов. Вместо этого мы загрузим изображение GIF, используя axios в качестве буфера, и вместо этого используем метод loadFromData QMovie.

Итак, приступим к установке axios:

npm i axios

Теперь давайте создадим функцию, которая будет принимать URL-адрес в качестве параметра и возвращать настроенный QMovieэкземпляр для GIF:

async function getMovie (url) {

const { data } = await axios.get (url, { responseType: 'arraybuffer’ }) ;

const movie = new QMovie () ;

movie.loadFromData (data) ;

movie.start () ;

return movie;

}

Функция getMovieпринимает URL-адрес, указывает axios загрузить GIF в качестве буфера, а затем использует этот буфер для создания QMovieэкземпляра.

Вы можете думать о QMovieнем как о классе, который обрабатывает внутреннюю логику воспроизведения GIF-анимации кадр за кадром. QMovieне является виджетом, поэтому его нельзя отобразить на экране как есть. Вместо этого мы будем использовать обычный QLabelэкземпляр и установим QMovieего.

Поскольку getMovieвозвращает обещание, нам нужно внести некоторые изменения в код. После небольшого рефакторинга получаем следующее.

источник/index.js

const { QMainWindow, QMovie, QLabel } = require ('@nodegui/nodegui’) ;

const axios = require ('axios’).default;

async function getMovie (url) {

const { data } = await axios.get (url, { responseType: 'arraybuffer’ }) ;

const movie = new QMovie () ;

movie.loadFromData (data) ;

movie.start () ;

return movie;

}

const main = async () => {

const win = new QMainWindow () ;

win.setWindowTitle ('Meme Search’) ;

const label = new QLabel () ;

const gifMovie = await getMovie (

'https: //upload.wikimedia.org/wikipedia/commons/e/e3/Animhorse.gif’

) ;

label.setMovie (gifMovie) ;

win.setCentralWidget (label) ;

win.show () ;

global.win = win;

};

main ().catch (console.error) ;

Функция main— это наша точка входа. Здесь мы создаем окно и метку. Затем мы создаем экземпляр QMovieс помощью нашей getMovieфункции и, наконец, устанавливаем QMovieзначение QLabel.

Запустите приложение, npm startи вы должны увидеть что-то вроде этого:

Пример базовой анимации, показывающей скачущую лошадь

Получение GIF-файлов из GIPHY API

У Giphy.com есть общедоступный API, который каждый может использовать для создания отличных приложений, использующих анимированные GIF-файлы. Чтобы использовать API GIPHY, вам необходимо зарегистрироваться на сайте developer.giphy.com и получить ключ API. Дальнейшие инструкции вы можете найти здесь.

Мы будем использовать функцию конечной точки поиска для реализации нашего поиска мемов.

Давайте начнем с написания searchGifsфункции, которая будет принимать searchTermsпараметр в качестве входных данных и запрашивать GIF-файлы, используя указанную выше конечную точку:

const GIPHY_API_KEY = 'Your API key here’;

async function searchGifs (searchTerm) {

const url = 'https: //api.giphy.com/v1/gifs/search’;

const res = await axios.get (url, {

params: {

api_key: GIPHY_API_KEY,

limit: 25,

q: searchTerm,

lang: 'en’,

offset: 0,

rating: 'pg-13'

}

}) ;

return res.data.data;

}

Результат функции после выполнения будет выглядеть примерно так:

[

{

«type»: «gif»,

«id»: «dzaUX7CAG0Ihi»,

«url»: «https: //giphy.com/gifs/hello-hi-dzaUX7CAG0Ihi»,

«images»: {

«fixed_width_small»: {

«height»: «54»,

«size»: «53544»,

«url»: «https: //media3.giphy.com/media/dzaUX7CAG0Ihi/100w.gif? cid=725ec7e0c00032f700929ce9f09f3f5fe5356af8c874ab12&rid=100w.gif»,

«width»: «100»

},

«downsized_large»: {

«height»: «220»,

«size»: «807719»,

«url»: «https: //media3.giphy.com/media/dzaUX7CAG0Ihi/giphy.gif? cid=725ec7e0c00032f700929ce9f09f3f5fe5356af8c874ab12&rid=giphy.gif»,

«width»: «410»

},

...

},

«slug»: «hello-hi-dzaUX7CAG0Ihi»,

...

«import_datetime»: «2016-01-07 15:40:35»,

«trending_datetime»: «1970-01-01 00:00:00»

},

{

type: «gif»,

...

},

...

]

В результате получается массив объектов, содержащих информацию о каждом GIF. Нас особенно интересует returnValue[i].images.fixed_width_small.urlдля каждого изображения, которое содержит URL-адрес GIF.

Отображение списка GIF-файлов с использованием ответа API

Чтобы отобразить список GIF-файлов, мы создадим getGifViewsфункцию, которая будет:

создать контейнер QWidget

создать QMovieвиджет для каждого GIF

создать QLabelиз каждого QMovieэкземпляра

прикрепите каждый QLabelкак дочерний элемент QWidgetконтейнера

вернуть QWidgetконтейнер

Код выглядит следующим образом:

async function getGifViews (listOfGifs) {

const container = new QWidget () ;

container.setLayout (new FlexLayout ());

const promises = listOfGifs.map (async gif => {

const { url, width } = gif.images.fixed_width_small;

const movie = await getMovie (url) ;

const gifView = new QLabel () ;

gifView.setMovie (movie) ;

gifView.setInlineStyle (`width: ${width}`) ;

container.layout.addWidget (gifView) ;

}) ;

await Promise.all (promises) ;

container.setInlineStyle (`

flex-direction: 'row’;

flex-wrap: 'wrap’;

justify-content: 'space-around’;

width: 330px;

height: 300px;

`) ;

return container;

}

Давайте немного разберем это.

Во-первых, мы создаем виджет-контейнер. QWidgets — это, по сути, пустые виджеты, которые действуют как контейнеры. Они похожи на

элементы в веб-мире.

 

Затем, чтобы назначить дочерние виджеты элементу QWidget, нам нужно задать ему макет. Макет определяет, как дочерние виджеты должны располагаться внутри родителя. Здесь мы выбираем FlexLayout.

Затем мы используем нашу getMovieфункцию для создания QMovieэкземпляра для каждого URL-адреса GIF. Мы назначаем QMovieэкземпляр a QLabel (с именем gifView) и придаем ему некоторые базовые стили, используя setInlineStyleметод. Наконец, мы добавляем QLabelвиджет в макет контейнера с помощью layout.addWidgetметода.

Поскольку все это происходит асинхронно, мы ждем, пока все разрешится с помощью Promise.all, прежде чем устанавливать некоторые стили контейнера и возвращать виджет контейнера.

Прикрепление списка к виджету нашего окна

Теперь давайте изменим нашу mainфункцию, чтобы увидеть список подготовленных нами виджетов.

источник/index.js

const { FlexLayout, QLabel, QMainWindow, QMovie, QWidget } = require ('@nodegui/nodegui’) ;

const axios = require ('axios’).default;

const GIPHY_API_KEY = 'Your API key here’;

async function getMovie (url) {... }

async function searchGifs (searchTerm) {... }

async function getGifViews (listOfGifs) {... }

const main = async () => {

const win = new QMainWindow () ;

win.setWindowTitle ('Meme Search’) ;

const center = new QWidget () ;

center.setLayout (new FlexLayout ());

// We get the list of gifs here

const listOfGifs = await searchGifs ('hello’) ;

// We create the container with GIF view widgets

const container = await getGifViews (listOfGifs) ;

// We finally attach the container to the widget

center.layout.addWidget (container) ;

win.setCentralWidget (center) ;

win.show () ;

global.win = win;

};

main ().catch (console.error) ;

Если вы запустите проект после внесения этих изменений, вы должны увидеть:

Список приветственных GIF-файлов, извлеченных из GIPHY API

Большой! Теперь давайте добавим поле ввода поиска вместе с кнопкой, чтобы пользователи могли искать что-то другое, кроме GIF-файлов «привет».

Добавление ввода поиска и кнопки

Начнем с создания createSearchContainerфункции, которая будет принимать функцию обратного вызова в качестве параметра. Это будет вызываться при нажатии кнопки поиска.

Вот что должна делать функция:

создайте QWidgetконтейнер, в который мы добавим поле ввода поиска и кнопку в качестве дочерних элементов

создать макет и прикрепить его к контейнеру

создайте поисковый ввод и кнопку, затем прикрепите их кFlexLayout

прикрепите прослушиватель событий к кнопке, которая при нажатии будет вызывать onSearchфункцию обратного вызова, передавая ей любой текст, присутствующий в поле ввода текста

вернуть QWidgetконтейнер

Код выглядит следующим образом:

function createSearchContainer (onSearch) {

const searchContainer = new QWidget () ;

searchContainer.setObjectName ('searchContainer’) ;

searchContainer.setLayout (new FlexLayout ());

const searchInput = new QLineEdit () ;

searchInput.setObjectName ('searchInput’) ;

const searchButton = new QPushButton () ;

searchButton.setObjectName ('searchButton’) ;

searchButton.setText (' 🔎 ') ;

searchButton.addEventListener ('clicked’, () => {

onSearch (searchInput.text ());

}) ;

searchContainer.layout.addWidget (searchInput) ;

searchContainer.layout.addWidget (searchButton) ;

searchContainer.setStyleSheet (`

#searchContainer {

flex-direction: 'row’;

padding: 10px;

align-items: 'center’;

}

#searchInput {

flex: 1;

height: 40px;

}

#searchButton {

margin-left: 5px;

width: 50px;

height: 35px;

}

`) ;

return searchContainer;

}

Надеюсь, у вас есть четкое представление о том, что здесь происходит, но обратите внимание на одну новую вещь — метод setStyleSheet. Вы можете думать об этом как о способе применить блочный CSS за один раз. Это очень похоже на глобальные таблицы стилей в Интернете, но с той разницей, что в NodeGui/Qt таблица стилей может быть присоединена к любому блоку, а не только глобально.

Чтобы стилизовать виджет с помощью таблицы стилей, нам нужно добавить objectNameк виджету, который мы будем использовать для ссылки на него в таблице стилей. Это в значительной степени идентично тому, что происходит idв веб-мире. Чтобы установить objectName, мы будем использовать setObjectNameметод.

Теперь давайте добавим это searchContainerв главное окно.

источник/index.js

const {

FlexLayout,

QLabel,

QLineEdit,

QMainWindow,

QMovie,

QPushButton,

QWidget,

} = require ('@nodegui/nodegui’) ;

const axios = require ('axios’).default;

const GIPHY_API_KEY = 'Your API key here’;

async function getMovie (url) {... }

async function searchGifs (searchTerm) {... }

async function getGifViews (listOfGifs) {... }

function createSearchContainer (onSearch) {... }

const main = async () => {

const win = new QMainWindow () ;

win.setWindowTitle ('Meme Search’) ;

const center = new QWidget () ;

center.setLayout (new FlexLayout ());

// Here we create the search container

const searchContainer = createSearchContainer (searchText => {

console.log ('searchText: ', searchText) ;

}) ;

// Here we add it to the center widget before we add the list of GIFs.

center.layout.addWidget (searchContainer) ;

const listOfGifs = await searchGifs ('hello’) ;

const container = await getGifViews (listOfGifs) ;

center.layout.addWidget (container) ;

win.setCentralWidget (center) ;

win.show () ;

global.win = win;

};

main ().catch (console.error) ;

Теперь, когда вы запускаете приложение и вводите что-то в поле поиска, вы должны увидеть, что все, что вы искали, регистрируется в вашем терминале.

Список GIF-файлов с поисковым вводом

Подключение поиска к просмотру GIF

Чтобы загружать новые GIF-файлы в ответ на поиск пользователя, нам нужно сделать следующее:

Внутри обратного вызова, который запускается при нажатии кнопки поиска, возьмите текст поиска и используйте searchGifsфункцию, чтобы получить новый список GIF-файлов.

Создайте новый контейнер для этих GIF-файлов с помощью getGifViewsфункции.

Удалите существующий контейнер из окна.

Добавьте новый контейнер в окно.

Если немного перетасовать, то получим:

const main = async () => {

const win = new QMainWindow () ;

win.setWindowTitle ('Meme Search’) ;

const center = new QWidget () ;

center.setLayout (new FlexLayout ());

let container = new QWidget () ;

const searchContainer = createSearchContainer (async searchText => {

try {

// Create a new GIF container with new GIFs

const listOfGifs = await searchGifs (searchText) ;

const newGifContainer = await getGifViews (listOfGifs) ;

// Remove existing container from the window

center.layout.removeWidget (container) ;

container.close () ;

// Add the new GIF container to the window

center.layout.addWidget (newGifContainer) ;

container = newGifContainer;

} catch (err) {

console.error ('Something happened!', err) ;

}

}) ;

center.layout.addWidget (searchContainer) ;

win.setCentralWidget (center) ;

win.show () ;

global.win = win;

};

Запустим еще раз и увидим волшебство 🧙‍♂️.

Подключенный виджет поиска GIF

Как видите, когда вы вводите что-то в поле поиска и нажимаете кнопку поиска, наш виджет извлечет список GIF-файлов, соответствующих поисковому запросу, из GIPHY API.

Использование QScrollAreaдля прокрутки списка GIF-файлов

Пока все движется в правильном направлении, вы, наверное, заметили, что список GIF-ок обрезан внизу и нет возможности их прокрутить. Это потому, что мы используем QWidgetконтейнер для их отображения. Чтобы сделать контейнер прокручиваемым, нам нужно заменить QScrollArea QWidgetна QScrollArea. Это обеспечивает просмотр другого виджета с прокруткой.

Мы начнем с удаления heightсвойства в getGifViewsфункции:

async function getGifViews (listOfGifs) {

...

container.setInlineStyle (`

flex-direction: 'row’;

flex-wrap: 'wrap’;

justify-content: 'space-around’;

width: 330px;

— height: 300px;

`) ;

return container;

}

Затем нам нужно измениться src/index.js, чтобы выглядеть так:

const {

FlexLayout,

QLabel,

QLineEdit,

QMainWindow,

QMovie,

QPushButton,

QScrollArea,

QWidget,

} = require ('@nodegui/nodegui’) ;

const axios = require ('axios’).default;

const GIPHY_API_KEY = 'Your API key here’;

async function getMovie (url) {... }

async function searchGifs (searchTerm) {... }

async function getGifViews (listOfGifs) {... }

function createSearchContainer (onSearch) {... }

const main = async () => {

const win = new QMainWindow () ;

win.setWindowTitle ('Meme Search’) ;

const center = new QWidget () ;

center.setLayout (new FlexLayout ());

const scrollArea = new QScrollArea () ;

scrollArea.setWidgetResizable (false) ;

scrollArea.setInlineStyle ('flex: 1; width: 350px; height: 400px;') ;

const searchContainer = createSearchContainer (async searchText => {

try {

const listOfGifs = await searchGifs (searchText) ;

const newGifContainer = await getGifViews (listOfGifs) ;

// Remove existing container from the scrollArea

const oldContainer = scrollArea.takeWidget () ;

if (oldContainer) oldContainer.close () ;

// Add the new GIF container to the scrollArea

scrollArea.setWidget (newGifContainer) ;

} catch (err) {

console.error ('Something happened!', err) ;

}

}) ;

center.layout.addWidget (searchContainer) ;

center.layout.addWidget (scrollArea) ;

win.setCentralWidget (center) ;

win.show () ;

global.win = win;

};

main ().catch (console.error) ;

Здесь нет ничего слишком захватывающего. Мы создаем новый элемент QScrollArea, который добавляем в макет под полем поиска. Мы также используем метод takeWidgetQScrollArea для удаления любого существующего контейнера из области прокрутки перед добавлением новых результатов поиска.

Если вы запустите поисковик мемов, теперь у вас должны быть прокручиваемые GIF-файлы:

Прокручиваемый поиск

Добавление прослушивателей кликов для копирования URL-адресов GIF для совместного использования

Теперь, когда мы можем видеть все GIF-файлы, мы хотим поделиться ими. Один из быстрых способов сделать это — копировать URL-адрес в глобальный буфер обмена всякий раз, когда пользователь щелкает GIF по своему выбору.

Затем пользователь может просто перейти к тому месту, где он хочет использовать GIF, и вставить его с помощью Ctrl/ Cmd+ V.

Для этого мы должны:

прикрепите прослушиватель событий мыши к каждому GIF

внутри обратного вызова прослушивателя событий используйте класс QClipboard, чтобы скопировать URL-адрес в глобальный буфер обмена

показать пользователю модальное сообщение о том, что URL-адрес был скопирован

Слушатель событий может быть прикреплен внутри getGifViewsфункции:

async function getGifViews (listOfGifs) {

...

const promises = listOfGifs.map (async gif => {

...

gifView.addEventListener (WidgetEventTypes.MouseButtonRelease, () => {

const clipboard = QApplication.clipboard () ;

clipboard.setText (url, QClipboardMode.Clipboard) ;

showModal (

'Copied to clipboard!',

`You can press Cmd/Ctrl + V to paste the GIF url: ${url}`

) ;

}) ;

container.layout.addWidget (gifView) ;

}) ;

...

return container;

}

Здесь QApplication.clipboard возвращает объект для взаимодействия с буфером обмена. Мы можем использовать setTextметод этого объекта, чтобы изменить фактическое содержимое буфера обмена.

Мы также используем showModalфункцию. Давайте определим, что дальше:

function showModal (title, details) {

const modal = new QMessageBox () ;

modal.setText (title) ;

modal.setDetailedText (details) ;

const okButton = new QPushButton () ;

okButton.setText ('OK’) ;

modal.addButton (okButton, ButtonRole.AcceptRole) ;

modal.exec () ;

}

Виджет QMessageBox похож на окно предупреждения в веб-браузере. Его можно использовать для прекращения взаимодействия с пользователем и отображения сообщения.

Наконец, нам нужно импортировать все эти новые виджеты вверху src/index.js:

const {

ButtonRole,

FlexLayout,

QApplication,

QClipboardMode,

QLabel,

QLineEdit,

QMainWindow,

QMessageBox,

QMovie,

QPushButton,

QScrollArea,

QWidget,

WidgetEventTypes,

} = require ('@nodegui/nodegui’) ;

const axios = require ('axios’).default;

const GIPHY_API_KEY = 'Your API key here’;

async function searchGifs (searchTerm) {... };

async function getGifViews (listOfGifs) {... };

async function getMovie (url) {... };

function createSearchContainer (onSearch) {... };

function showModal (title, details) {... };

const main = async () => {... };

main ().catch (console.error) ;

Если вы запустите поисковик мемов, теперь у вас должна быть возможность копировать/вставлять URL-адреса GIF:

Скопировать URL GIF в буфер обмена GIF

Добавление значка в системный трей

Мы хотим, чтобы наше приложение было скрыто в системном трее, когда оно не используется. Для этого мы создадим значок на панели задач, который будет иметь пункт меню, который при щелчке будет переключать видимость работающего виджета.

Необходимые шаги:

Создайте QSystemTrayIcon со значком.

Создайте меню для значка на панели задач, используя QMenu. Установите экземпляр меню в качестве контекстного меню панели задач.

Создавайте пункты меню с помощью виджетов QAction и настройте прослушиватели событий для прослушивания их triggerсобытий.

По триггеру скрыть или показать окно.

Давайте начнем с запроса необходимых модулей, а затем внесем небольшое изменение в mainфункцию, чтобы указать ей использовать нашу иконку:

const {

ButtonRole,

FlexLayout,

QApplication,

QClipboardMode,

QIcon,

QLabel,

QLineEdit,

QMainWindow,

QMenu,

QMessageBox,

QMovie,

QAction,

QPushButton,

QScrollArea,

QSystemTrayIcon,

QWidget,

WidgetEventTypes,

} = require ('@nodegui/nodegui’) ;

const axios = require ('axios’).default;

const path = require ('path’) ;

const iconImg = require ('.../assets/systray.png’).default;

const GIPHY_API_KEY = 'Your API key here’;

const main = async () => {

...

win.show () ;

systemTrayIcon (win) ;

global.win = win;

};

Как видите, нам нужна иконка из assetsпапки. Если вы следуете инструкциям, вы можете скачать файл значка отсюда.

Теперь идет функция для создания значка в системном трее:

function systemTrayIcon (win) {

const icon = new QIcon (path.resolve (__dirname, iconImg));

const tray = new QSystemTrayIcon () ;

tray.setIcon (icon) ;

tray.show () ;

// Menu that should pop up when clicking on systray icon.

const menu = new QMenu () ;

tray.setContextMenu (menu) ;

//Each item in the menu is called an action

const visibleAction = new QAction () ;

menu.addAction (visibleAction) ;

visibleAction.setText ('Show/Hide’) ;

visibleAction.addEventListener ('triggered’, () => {

if (win.isVisible ()) {

win.hide () ;

} else {

win.show () ;

}

}) ;

global.tray = tray;

}

Здесь мы создаем значок, используя класс NodeGui QIcon. Затем мы используем QSystemTrayIconкласс для создания значка на панели задач для нашего приложения.

Наконец, нам нужно настроить параметры нашего веб-пакета (в webpack.config.js), чтобы предотвратить полифиллинг веб-пакета __dirname:

const path = require ('path’) ;

module.exports = {

...

node: {

— __dirname: true,

— __filename: true

+ __dirname: false,

+ __filename: false

},

...

}

Окончательный результат:

Окончательный виджет поиска

Некоторые последние настройки

Обработка ошибок

Прежде чем мы перейдем к упаковке, давайте воспользуемся нашей showModalфункцией и добавим диалоговое окно обработки ошибок:

const main = async () => {

...

const searchContainer = createSearchContainer (async searchText => {

try {

...

} catch (err) {

...

showModal ('Something went wrong!', JSON.stringify (err));

}

}) ;

...

};

Это предупредит пользователя, если, например, что-то пойдет не так с запросом Ajax на получение GIF-файлов из GIPHY. Вы можете попробовать это, изменив свой ключ API на что-то недействительное, затем запустив приложение и попытавшись найти GIF.

Разрешить пользователю вводить ключ API

Пока мы говорим о ключах API, давайте добавим диалоговое окно, позволяющее пользователю вводить свой ключ API. Это означает, что его не нужно жестко запрограммировать в программе:

const {

...

QDialog,

...

} = require ('@nodegui/nodegui’) ;

...

let GIPHY_API_KEY = '';

async function searchGifs (searchTerm) {... }

async function getGifViews (listOfGifs) {... }

async function getMovie (url) {... }

function createSearchContainer (onSearch) {... }

function showModal (title, details) {... }

function systemTrayIcon (win) {... }

function showAPIKeyDialog () {

const dialog = new QDialog () ;

dialog.setLayout (new FlexLayout ());

const label = new QLabel () ;

label.setText ('Enter your Giphy API Key’) ;

const input = new QLineEdit () ;

const okButton = new QPushButton () ;

okButton.setText ('OK’) ;

okButton.addEventListener ('clicked’, () => {

GIPHY_API_KEY = input.text () ;

dialog.close () ;

}) ;

dialog.layout.addWidget (label) ;

dialog.layout.addWidget (input) ;

dialog.layout.addWidget (okButton) ;

dialog.setInlineStyle (`

padding: 10;

height: 150px;

flex-direction: 'column’;

align-items:'center’;

justify-content: 'space-around’;

`) ;

dialog.exec () ;

}

const main = async () => {

...

showAPIKeyDialog () ;

global.win = win;

};

main ().catch (console.error) ;

Как видите, мы используем QDialogвиджет, чтобы запрашивать у пользователя ввод данных, а затем сохраняем все, что он предоставляет, в GIPHY_API_KEYпеременной. Если вы хотите улучшить свои навыки работы с NodeGui после прочтения этого руководства, вы можете улучшить его — например, сохранив ключ к файловой системе или проверив его и предоставив отзыв пользователю.

Примечание. Не забывайте, что полный исходный код доступен здесь: https://github.com/sitepoint-editors/memesearchapp-nodegui-tutorial.

Упаковка приложения для кросс-платформенного распространения

После того, как мы успешно создали приложение, нам нужно создать дистрибутивы для macOS, Windows и Linux, которые конечные пользователи смогут загрузить и использовать.

Процесс создания дистрибутивов обычно различен для каждой операционной системы, поэтому, чтобы облегчить боль, мы будем использовать инструмент упаковки NodeGui под названием @nodegui/packer.

использование

Во-первых, установите упаковщик как зависимость от разработчика:

npm install —save-dev @nodegui/packer

Затем используйте упаковщик для создания шаблона развертывания:

npx nodegui-packer —init MemeApp

Шаблон, по сути, является проектом для конкретной ОС, который содержит код для успешной упаковки всего кода, ресурсов и зависимостей приложения NodeGui. Обратите внимание, что вам нужно запустить это в Windows, macOS и Linux отдельно, чтобы создать три разных шаблона. Этот шаблон позволяет точно настроить окончательные параметры развертывания для каждой ОС. Вы можете настроить такие вещи, как информация о компании, значки и другие метаданные, в соответствии с вашими потребностями.

Для Linux шаблон выглядит так:

.

└── deploy

├── config.json

└── linux

└── MemeApp

├── default.desktop

├── default.png

└── qode.json

Обратите внимание, что вам нужно запустить команду инициализации только один раз. Затем вы вносите изменения в шаблон и фиксируете его в репозитории проекта.

Следующим шагом является фактическая сборка и упаковка проекта в дистрибутив.

Удалите buildкаталог, если он существует:

rm -rf. /deploy/build

Затем создайте приложение с помощью веб-пакета:

npm run build

Наконец, запустите команду pack упаковщика, передав ей distпапку в качестве аргумента:

npx nodegui-packer —pack. /dist

Это приведет к следующему:

В macOS упаковщик выведет dmgфайл.

В Linux упаковщик выводит файл AppImage, который чем-то похож на.appфайл в macOS.

В Windows упаковщик выводит папку, содержащую исполняемый файл и все dll.

После успешного выполнения команды она должна распечатать выходной каталог, который обычно находится внутри deploy//buildкаталога. Убедитесь, что вы не фиксируете этот каталог:

.

└── deploy

├── config.json

└── linux

├── build

│ └── MemeApp

│ ├── Application-aed23d8-x86_64.AppImage

│ ├── AppRun → qode

│ ├── default.desktop

│ ├── default.png

│ ├── dist

│ │ ├── f59514675cec2e70ce8598286c94dc22.png

│ │ ├── index.js

│ │ └── nodegui_core-7b3e73f5fef149ae765d5ea5d13d5bb0.node

│ ├── doc

│ │ └──...

│ ├── lib

│ │ └──...

│ ├── plugins

│ │ └──...

│ ├── qode

│ ├── qode.json

│ └── qt.conf

└── MemeApp

├── default.desktop

├── default.png

└── qode.json

Дистрибутив Linux — deploy/linux/build/MemeApp/Application-aed23d8-x86_64.AppImage🚀📦.

Вывод

В этом руководстве мы успешно создали приложение для поиска мемов в реальном мире, используя NodeGui примерно из 200 строк кода. Мы изучили некоторые основные понятия и возможности библиотеки. Мы также смогли упаковать готовое приложение в дистрибутив, которым можно поделиться с конечными пользователями.

Я считаю, что NodeGui открывает двери для создания множества действительно эффективных нативных приложений с помощью Node.js.

NodeGui также поддерживает библиотеки и фреймворки, такие как React (официальный), Angular (сообщество) и вскоре Vue.js (сообщество). Пожалуйста, ознакомьтесь с ними и поставьте им звезду на GitHub, если они вас интересуют.

NodeGui — это библиотека с открытым исходным кодом, которая значительно выиграет от добавления кода. Он имеет относительно простую для понимания кодовую базу и очень гостеприимное сообщество. Я призываю всех помочь.

Наконец, благодаря множеству встроенных виджетов и стилям с помощью CSS, я считаю, что приложения NodeGui так же легко разрабатывать, как веб-приложения или приложения Electron. Я призываю вас создать что-то свое собственное и поделиться этим с нами.

Делитесь нашими материалами с друзьями!

 

 

Заказать разработку сайта