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
15 changes: 15 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,18 @@ jobs:
release/GetBox-*-portable.exe
release/GetBox-*-portable.exe.sha256
fail_on_unmatched_files: true
# Rebuild getbox.ne-nas.ru with the new version right away (GetBoxLanding's
# release-sync workflow). Needs a token with access to GetBoxLanding in the
# LANDING_DISPATCH_TOKEN secret; skipped without it — the site then catches
# up within an hour on its own.
- name: Update landing
env:
LANDING_TOKEN: ${{ secrets.LANDING_DISPATCH_TOKEN }}
if: env.LANDING_TOKEN != ''
shell: bash
run: >
curl -fsS -X POST
-H "Authorization: Bearer $LANDING_TOKEN"
-H "Accept: application/vnd.github+json"
https://api.github.com/repos/IncludeSpecial/GetBoxLanding/dispatches
-d '{"event_type":"getbox-release"}'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ build/

# Generated icons (from data/logo.svg)
data/logo.png
data/logo.ico
data/bootstrap/

# Caches & logs
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.3.1

### Внешний вид
- Новый логотип: плитка в стиле Windows 11 с открытой коробкой и стрелкой. Старый тёмный значок терялся на тёмной панели задач и не читался в 16–32 px.
- Иконка exe и окна — многоразмерный `.ico` (16–256 px); для 16–32 px нарисован упрощённый вариант (`data/logo-small.svg`), он же стоит в заголовке окна.
- Скриншоты в README пересняты; `promo/docs-screenshots.cjs` собирает их из кадров `promo/capture13.cjs`.

## 1.3.0

### GetBox обновляется сам
Expand Down
7 changes: 7 additions & 0 deletions data/logo-small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 18 additions & 34 deletions data/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions data/whats-new.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
{
"1.3.1": [
{ "icon": "palette", "title": "Новый значок", "text": "GetBox сменил логотип: он чётко виден в панели задач — и на тёмной, и на светлой.",
"titleEn": "New icon", "textEn": "GetBox has a new logo that stays crisp in the taskbar, light or dark." },
{ "icon": "refresh", "title": "Обновления программ", "text": "Всё, что можно обновить, — на одной вкладке. История и откат в один клик.", "action": "updates", "actionLabel": "Открыть обновления",
"titleEn": "Program updates", "textEn": "Everything that can be updated in one tab, with history and one-click rollback.", "actionLabelEn": "Open updates" },
{ "icon": "palette", "title": "Пять тем и свой акцент", "text": "Светлая, OLED, высокий контраст, Windows 11, свой цвет и масштаб.", "action": "settings", "actionLabel": "Выбрать тему",
"titleEn": "Five themes and your accent", "textEn": "Light, OLED, high contrast, Windows 11, your own colour and scale.", "actionLabelEn": "Pick a theme" },
{ "icon": "grid", "title": "Четыре вида списка", "text": "Карточки, список с описанием, таблица и плитки — переключатель над любым списком.", "action": "catalog", "actionLabel": "Попробовать",
"titleEn": "Four list views", "textEn": "Cards, a detailed list, a table and tiles — switch above any list.", "actionLabelEn": "Try it" },
{ "icon": "shield", "title": "GetBox обновляется сам", "text": "Новая версия скачивается в фоне и проверяется по SHA256 — кнопка появится в заголовке окна.",
"titleEn": "GetBox updates itself", "textEn": "New versions download in the background and are checked by SHA256 — a button appears in the title bar." }
],
"1.3.0": [
{ "icon": "refresh", "title": "Обновления программ", "text": "Всё, что можно обновить, — на одной вкладке. История и откат в один клик.", "action": "updates", "actionLabel": "Открыть обновления",
"titleEn": "Program updates", "textEn": "Everything that can be updated in one tab, with history and one-click rollback.", "actionLabelEn": "Open updates" },
Expand Down
1 change: 1 addition & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ npm run dist
2. После слияния в `main` поставьте тег `git tag vX.Y.Z && git push origin vX.Y.Z` — или запустите workflow **Release** вручную на вкладке *Actions*: тег `v<версия из package.json>` создастся сам.
3. Workflow `release.yml` на `windows-latest` прогонит проверки, соберёт `GetBox-X.Y.Z-portable.exe`, посчитает `GetBox-X.Y.Z-portable.exe.sha256` и опубликует релиз «GetBox X.Y.Z» с разделом из `CHANGELOG.md`.
4. Добавьте карточки для экрана «Что нового» в `data/whats-new.json` (ключ — версия), иначе после обновления экран не покажется.
5. Сайт [getbox.ne-nas.ru](https://getbox.ne-nas.ru/) подхватит релиз сам: последний шаг workflow просит [GetBoxLanding](https://github.com/IncludeSpecial/GetBoxLanding) пересобраться (нужен секрет `LANDING_DISPATCH_TOKEN`), без секрета сайт обновится в течение часа.

Самообновление берёт только релизы с ассетом `GetBox-*-portable.exe` и скачивает его, лишь если есть `.sha256` (или `digest` ассета от GitHub). Пре-релизы получают пользователи канала «Бета». Не удаляйте `.sha256` из релизов: без него клиенты не обновятся.

Expand Down
Binary file modified docs/screenshots/appearance.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/catalog.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/details.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/home.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/packages.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/profiles.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/queue.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/themes.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/updates.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ function createWindow(): void {
minWidth: 960,
minHeight: 600,
backgroundColor: '#0a1118',
icon: path.join(getDataDir(), 'logo.png'),
// Multi-size .ico keeps the taskbar icon crisp on Windows.
icon: path.join(getDataDir(), process.platform === 'win32' ? 'logo.ico' : 'logo.png'),
frame: false,
webPreferences: {
preload: preloadPath,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "getbox",
"version": "1.3.0",
"version": "1.3.1",
"description": "GetBox — локальный магазин программ для Windows",
"main": "dist-electron/main.js",
"type": "module",
Expand All @@ -21,7 +21,7 @@
"build": {
"appId": "com.getbox.app",
"productName": "GetBox",
"icon": "build/icon.png",
"icon": "build/icon.ico",
"directories": {
"output": "release"
},
Expand Down
3 changes: 3 additions & 0 deletions promo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ python3 promo/music13.py
ffmpeg -i promo/promo13-240p.mp4 -i promo/music13.wav -c:v copy -c:a aac -b:a 96k -shortest promo/GetBox-1.3-preview-240p.mp4
# 1.3 — финал: QUERY="h=1080" node promo/render.cjs video promo13.html

# Скриншоты для README (docs/screenshots/*.webp) — из кадров capture13
node promo/docs-screenshots.cjs

# 1.3 — эдит 360p
QUERY="h=360" OUT_SUFFIX=-360p CRF=24 node promo/render.cjs video edit13.html
python3 promo/music-edit13.py
Expand Down
3 changes: 3 additions & 0 deletions promo/capture13.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ server.listen(0, async () => {
await shot(browser, base, 'modal-light', { owned: me, look: { theme: 'light' }, act: async (p) => {
await catalogTab(p); await p.waitForTimeout(300); await p.locator('.capsule').first().click();
} });
await shot(browser, base, 'modal', { owned: me, look: { listView: 'tiles' }, act: async (p) => {
await catalogTab(p); await p.waitForTimeout(300); await p.locator('.capsule').first().click();
} });
fs.writeFileSync(path.join(OUT, 'marks.json'), JSON.stringify(MARKS));
await browser.close(); server.close();
});
52 changes: 52 additions & 0 deletions promo/docs-screenshots.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Builds docs/screenshots/*.webp (1440×900) from the promo/shots/13 captures,
// including the five-theme strip. Run after `node promo/capture13.cjs`.
// Encoding happens in Chromium (canvas → WebP), so no image library is needed.
const { chromium } = require(process.env.PLAYWRIGHT_MODULE ?? 'playwright');
const launchOpts = process.env.CHROMIUM_PATH ? { executablePath: process.env.CHROMIUM_PATH } : {};
const fs = require('fs');
const path = require('path');

const SHOTS = path.join(__dirname, 'shots', '13');
const OUT = path.join(__dirname, '..', 'docs', 'screenshots');

// docs name → capture name(s); several captures make vertical slices, left to right.
const MAP = {
home: ['home-classic'],
catalog: ['cat-tiles'],
details: ['modal'],
queue: ['install-queue'],
updates: ['updates'],
packages: ['feat-packages'],
profiles: ['feat-profiles'],
appearance: ['L-appearance'],
themes: ['home-classic', 'home-light', 'home-oled', 'home-contrast', 'home-mica'],
};

const dataUrl = (name) => `data:image/png;base64,${fs.readFileSync(path.join(SHOTS, `${name}.png`)).toString('base64')}`;

(async () => {
const browser = await chromium.launch(launchOpts);
const page = await browser.newPage();
fs.mkdirSync(OUT, { recursive: true });
for (const [doc, parts] of Object.entries(MAP)) {
const webp = await page.evaluate(async (srcs) => {
const W = 1440, H = 900;
const canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
ctx.imageSmoothingQuality = 'high';
const slice = W / srcs.length;
for (let i = 0; i < srcs.length; i++) {
const img = new Image();
img.src = srcs[i];
await img.decode();
const k = img.width / W; // captures are 2× (2880×1800)
ctx.drawImage(img, i * slice * k, 0, slice * k, img.height, i * slice, 0, slice, H);
}
return canvas.toDataURL('image/webp', 0.86);
}, parts.map(dataUrl));
fs.writeFileSync(path.join(OUT, `${doc}.webp`), Buffer.from(webp.split(',')[1], 'base64'));
console.log('docs', `${doc}.webp`);
}
await browser.close();
})();
54 changes: 40 additions & 14 deletions scripts/render-app-icon.cjs
Original file line number Diff line number Diff line change
@@ -1,23 +1,49 @@
/**
* Renders data/logo.svg → build/icon.png and data/logo.png (256×256)
* for electron-builder and BrowserWindow.
* Renders the app icon from data/logo.svg (and data/logo-small.svg for 16–32 px):
* build/icon.ico, data/logo.ico — multi-size Windows icon for the exe and window
* build/icon.png, data/logo.png — 256×256 PNG
*/
const fs = require('fs');
const path = require('path');
const { Resvg } = require('@resvg/resvg-js');

const root = path.join(__dirname, '..');
const svgPath = path.join(root, 'data', 'logo.svg');
const outBuild = path.join(root, 'build', 'icon.png');
const outData = path.join(root, 'data', 'logo.png');
const full = fs.readFileSync(path.join(root, 'data', 'logo.svg'));
const small = fs.readFileSync(path.join(root, 'data', 'logo-small.svg'));

const svg = fs.readFileSync(svgPath);
const resvg = new Resvg(svg, {
fitTo: { mode: 'width', value: 256 },
});
const png = resvg.render().asPng();
const render = (svg, size) => new Resvg(svg, { fitTo: { mode: 'width', value: size } }).render().asPng();

fs.mkdirSync(path.dirname(outBuild), { recursive: true });
fs.writeFileSync(outBuild, png);
fs.writeFileSync(outData, png);
console.log('app icon:', path.relative(root, outBuild), path.relative(root, outData));
/** ICO container with PNG-compressed entries (supported since Windows Vista). */
function ico(images) {
const header = Buffer.alloc(6);
header.writeUInt16LE(0, 0);
header.writeUInt16LE(1, 2);
header.writeUInt16LE(images.length, 4);
const dir = Buffer.alloc(16 * images.length);
let offset = header.length + dir.length;
images.forEach(({ size, png }, i) => {
const e = i * 16;
dir.writeUInt8(size >= 256 ? 0 : size, e);
dir.writeUInt8(size >= 256 ? 0 : size, e + 1);
dir.writeUInt16LE(1, e + 4); // colour planes
dir.writeUInt16LE(32, e + 6); // bits per pixel
dir.writeUInt32LE(png.length, e + 8);
dir.writeUInt32LE(offset, e + 12);
offset += png.length;
});
return Buffer.concat([header, dir, ...images.map((i) => i.png)]);
}

const sizes = [16, 20, 24, 32, 40, 48, 64, 128, 256];
const icon = ico(sizes.map((size) => ({ size, png: render(size <= 32 ? small : full, size) })));
const png256 = render(full, 256);

const out = [
['build/icon.ico', icon],
['data/logo.ico', icon],
['build/icon.png', png256],
['data/logo.png', png256],
];
fs.mkdirSync(path.join(root, 'build'), { recursive: true });
for (const [file, data] of out) fs.writeFileSync(path.join(root, file), data);
console.log('app icon:', out.map(([f]) => f).join(', '));
3 changes: 2 additions & 1 deletion src/components/TitleBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import logoUrl from '../../data/logo.svg';
// 18 px: the simplified mark stays crisp where the full one turns to mush.
import logoUrl from '../../data/logo-small.svg';
import { IconClose, IconMaximize, IconMinimize, IconRestore } from './Icons';

function waitForApi(): Promise<typeof window.getbox> {
Expand Down
Loading