feat(agsV2): migrate screenshot window
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-10-28 14:45:10 -04:00
parent c17a45e5d0
commit 653961ada1
15 changed files with 313 additions and 108 deletions

View file

@ -12,6 +12,7 @@ import Corners from './widgets/corners/main';
import IconBrowser from './widgets/icon-browser/main';
import { NotifPopups, NotifCenter } from './widgets/notifs/main';
import PowerMenu from './widgets/powermenu/main';
import Screenshot from './widgets/screenshot/main';
import MonitorClicks from './services/monitor-clicks';
@ -48,6 +49,7 @@ switch (CONF) {
NotifPopups();
NotifCenter();
PowerMenu();
Screenshot();
new MonitorClicks();
},

View file

@ -51,7 +51,7 @@ self: {
})
// {
"${configDir}/node_modules".source =
buildNodeModules ./. "sha256-f0hbPvHTqeFM7mfmV+sN4EEuE0F91f5kjJ/EHy0oU+Y=";
buildNodeModules ./. "sha256-pK9S6qUjTIL0JDegYJlHSY5XEpLFKfA98MfZ59Q3IL4=";
"${configDir}/tsconfig.json".source = pkgs.writers.writeJSON "tsconfig.json" {
"$schema" = "https://json.schemastore.org/tsconfig";

View file

@ -1,4 +1,6 @@
import { Gdk } from 'astal/gtk3';
import { App, Gdk, Widget } from 'astal/gtk3';
import AstalApps from 'gi://AstalApps';
import AstalHyprland from 'gi://AstalHyprland';
const Hyprland = AstalHyprland.get_default();
@ -109,3 +111,16 @@ export const centerCursor = async(): Promise<void> => {
await hyprMessage(`dispatch movecursor ${x} ${y}`);
};
export const get_app_icon = (app: AstalApps.Application): string => {
if (app.get_icon_name() && Widget.Icon.lookup_icon(app.get_icon_name())) {
return app.get_icon_name();
}
else {
const iconString = app.get_key('Icon');
App.add_icons(iconString);
return iconString;
}
};

View file

@ -11,13 +11,13 @@
"@eslint/js": "9.13.0",
"@stylistic/eslint-plugin": "2.9.0",
"@types/eslint__js": "8.42.3",
"@types/node": "22.7.7",
"@types/node": "22.8.1",
"eslint": "9.13.0",
"eslint-plugin-jsdoc": "50.4.3",
"fzf": "0.5.2",
"jiti": "2.3.3",
"typescript": "5.6.3",
"typescript-eslint": "8.10.0"
"typescript-eslint": "8.11.0"
}
},
"node_modules/@es-joy/jsdoccomment": {
@ -336,25 +336,25 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.7.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.7.tgz",
"integrity": "sha512-SRxCrrg9CL/y54aiMCG3edPKdprgMVGDXjA3gB8UmmBW5TcXzRUYAh8EWzTnSJFAd1rgImPELza+A3bJ+qxz8Q==",
"version": "22.8.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz",
"integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
"undici-types": "~6.19.8"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz",
"integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz",
"integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==",
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.10.0",
"@typescript-eslint/type-utils": "8.10.0",
"@typescript-eslint/utils": "8.10.0",
"@typescript-eslint/visitor-keys": "8.10.0",
"@typescript-eslint/scope-manager": "8.11.0",
"@typescript-eslint/type-utils": "8.11.0",
"@typescript-eslint/utils": "8.11.0",
"@typescript-eslint/visitor-keys": "8.11.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@ -378,15 +378,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz",
"integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz",
"integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==",
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "8.10.0",
"@typescript-eslint/types": "8.10.0",
"@typescript-eslint/typescript-estree": "8.10.0",
"@typescript-eslint/visitor-keys": "8.10.0",
"@typescript-eslint/scope-manager": "8.11.0",
"@typescript-eslint/types": "8.11.0",
"@typescript-eslint/typescript-estree": "8.11.0",
"@typescript-eslint/visitor-keys": "8.11.0",
"debug": "^4.3.4"
},
"engines": {
@ -406,13 +406,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz",
"integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz",
"integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.10.0",
"@typescript-eslint/visitor-keys": "8.10.0"
"@typescript-eslint/types": "8.11.0",
"@typescript-eslint/visitor-keys": "8.11.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -423,13 +423,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz",
"integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz",
"integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.10.0",
"@typescript-eslint/utils": "8.10.0",
"@typescript-eslint/typescript-estree": "8.11.0",
"@typescript-eslint/utils": "8.11.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@ -447,9 +447,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz",
"integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz",
"integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -460,13 +460,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz",
"integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz",
"integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==",
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "8.10.0",
"@typescript-eslint/visitor-keys": "8.10.0",
"@typescript-eslint/types": "8.11.0",
"@typescript-eslint/visitor-keys": "8.11.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@ -488,15 +488,15 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz",
"integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz",
"integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.10.0",
"@typescript-eslint/types": "8.10.0",
"@typescript-eslint/typescript-estree": "8.10.0"
"@typescript-eslint/scope-manager": "8.11.0",
"@typescript-eslint/types": "8.11.0",
"@typescript-eslint/typescript-estree": "8.11.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -510,12 +510,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz",
"integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz",
"integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.10.0",
"@typescript-eslint/types": "8.11.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@ -1665,14 +1665,14 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.10.0.tgz",
"integrity": "sha512-YIu230PeN7z9zpu/EtqCIuRVHPs4iSlqW6TEvjbyDAE3MZsSl2RXBo+5ag+lbABCG8sFM1WVKEXhlQ8Ml8A3Fw==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.11.0.tgz",
"integrity": "sha512-cBRGnW3FSlxaYwU8KfAewxFK5uzeOAp0l2KebIlPDOT5olVi65KDG/yjBooPBG0kGW/HLkoz1c/iuBFehcS3IA==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.10.0",
"@typescript-eslint/parser": "8.10.0",
"@typescript-eslint/utils": "8.10.0"
"@typescript-eslint/eslint-plugin": "8.11.0",
"@typescript-eslint/parser": "8.11.0",
"@typescript-eslint/utils": "8.11.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View file

@ -6,12 +6,12 @@
"@eslint/js": "9.13.0",
"@stylistic/eslint-plugin": "2.9.0",
"@types/eslint__js": "8.42.3",
"@types/node": "22.7.7",
"@types/node": "22.8.1",
"eslint": "9.13.0",
"eslint-plugin-jsdoc": "50.4.3",
"fzf": "0.5.2",
"jiti": "2.3.3",
"typescript": "5.6.3",
"typescript-eslint": "8.10.0"
"typescript-eslint": "8.11.0"
}
}

View file

@ -134,23 +134,25 @@ export default class MonitorClicks extends GObject.Object {
overlayLayer.find(
(n) => n.namespace.startsWith(name),
) ||
// Return an empty Layer if widget doesn't exist
{
address: '',
x: 0,
y: 0,
w: 0,
h: 0,
namespace: '',
},
// Return an empty Layer if widget doesn't exist
{
address: '',
x: 0,
y: 0,
w: 0,
h: 0,
namespace: '',
},
);
});
return arr;
};
const clickIsOnWidget = (w: Layer) => {
return pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h;
return (
pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h
);
};
const noCloseWidgets = getNoCloseWidgets(noCloseWidgetsNames);
@ -158,24 +160,25 @@ export default class MonitorClicks extends GObject.Object {
const widgets = overlayLayer.filter((n) => {
let window = null as null | PopupWindow;
if (App.get_windows().some((win) =>
win.name === n.namespace)) {
if (App.get_windows().some((win) => win.name === n.namespace)) {
window = (App.get_window(n.namespace) as PopupWindow);
}
return window &&
window.close_on_unfocus &&
window.close_on_unfocus ===
clickStage;
window.close_on_unfocus &&
window.close_on_unfocus ===
clickStage;
});
if (noCloseWidgets.some(clickIsOnWidget)) {
// Don't handle clicks when on certain widgets
// Don't handle clicks when on certain widgets
}
else {
widgets.forEach((w) => {
if (!(pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h)) {
if (!(
pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h
)) {
App.get_window(w.namespace)?.set_visible(false);
}
});

View file

@ -19,3 +19,4 @@ window, viewport {
@import 'widgets/lockscreen/style.scss';
@import 'widgets/notifs/style.scss';
@import 'widgets/powermenu/style.scss';
@import 'widgets/screenshot/style.scss';

View file

@ -1,6 +1,8 @@
import { App, Gtk, Widget } from 'astal/gtk3';
import { Gtk, Widget } from 'astal/gtk3';
import { register } from 'astal/gobject';
import { get_app_icon } from '../../lib';
/* Types */
import AstalApps from 'gi://AstalApps';
type AppItemProps = Widget.BoxProps & {
@ -26,18 +28,11 @@ export class AppItem extends Widget.Box {
this.app = app;
const icon = (
<icon css="font-size: 42px; margin-right: 25px;" />
) as Widget.Icon;
if (app.get_icon_name() && Widget.Icon.lookup_icon(app.get_icon_name())) {
icon.icon = this.app.get_icon_name();
}
else {
const iconString = this.app.get_key('Icon');
App.add_icons(iconString);
icon.icon = iconString;
}
<icon
icon={get_app_icon(this.app)}
css="font-size: 42px; margin-right: 25px;"
/>
);
const textBox = (
<box

View file

@ -122,6 +122,7 @@ export default () => {
<button
css="margin-left: 5px;"
cursor="pointer"
onButtonReleaseEvent={refreshApplications}
>
<icon icon="view-refresh-symbolic" css="font-size: 26px;" />
@ -129,18 +130,20 @@ export default () => {
</box>
<scrollable
className="widget app-list"
<eventbox cursor="pointer">
<scrollable
className="widget app-list"
css="min-height: 600px; min-width: 600px;"
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
>
<box vertical>
{list}
{placeholder}
</box>
</scrollable>
css="min-height: 600px; min-width: 600px;"
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
>
<box vertical>
{list}
{placeholder}
</box>
</scrollable>
</eventbox>
</box>
</PopupWindow>
);

View file

@ -1,4 +1,6 @@
.bar {
margin-left: 5px;
margin-right: 15px;
margin-bottom: 13px;
.bar-item {

View file

@ -24,11 +24,7 @@
}
.cal-box {
border-radius: 30px;
padding: 0 1rem .2rem;
background-color: $window_bg_color;
border-bottom: 2px solid $accent_color;
border-top: 2px solid $accent_color;
margin: 0 12px 18px;
calendar {
@ -36,7 +32,6 @@
background-color: inherit;
padding: .5rem .10rem 0;
margin-left: 10px;
border-radius: 30px;
&>* {
border: solid 0 transparent;

View file

@ -67,8 +67,6 @@
.notification-list-box {
padding: 0 12px;
border-radius: 30px;
border-top: 2px solid $accent-color;
.notification {
box-shadow: none;

View file

@ -10,6 +10,7 @@ const PowermenuWidget = () => (
<centerbox className="powermenu widget">
<button
className="shutdown button"
cursor="pointer"
onButtonReleaseEvent={() => execAsync(['systemctl', 'poweroff']).catch(print)}
>
<icon icon="system-shutdown-symbolic" />
@ -17,6 +18,7 @@ const PowermenuWidget = () => (
<button
className="reboot button"
cursor="pointer"
onButtonReleaseEvent={() => execAsync(['systemctl', 'reboot']).catch(print)}
>
<icon icon="system-restart-symbolic" />
@ -24,6 +26,7 @@ const PowermenuWidget = () => (
<button
className="logout button"
cursor="pointer"
onButtonReleaseEvent={() => hyprMessage('dispatch exit').catch(print)}
>
<icon icon="system-log-out-symbolic" />

View file

@ -0,0 +1,163 @@
import { bind, execAsync, Variable } from 'astal';
import { App, Gtk, Widget } from 'astal/gtk3';
import AstalApps from 'gi://AstalApps';
const Applications = AstalApps.Apps.new();
import AstalHyprland from 'gi://AstalHyprland';
const Hyprland = AstalHyprland.get_default();
import PopupWindow from '../misc/popup-window';
import Separator from '../misc/separator';
import { get_app_icon, hyprMessage } from '../../lib';
const ICON_SEP = 6;
const takeScreenshot = (selector: string, delay = 1000): void => {
App.get_window('win-screenshot')?.set_visible(false);
setTimeout(() => {
execAsync([
'bash',
'-c',
`grim ${selector} - | satty -f - || true`,
]).catch(console.error);
}, delay);
};
export default () => {
const windowList = <box vertical /> as Widget.Box;
const updateWindows = async() => {
if (!App.get_window('win-screenshot')?.visible) {
return;
}
windowList.children = (JSON.parse(await hyprMessage('j/clients')) as AstalHyprland.Client[])
.filter((client) => client.workspace.id === Hyprland.get_focused_workspace().get_id())
.map((client) => (
<button
className="item-btn"
cursor="pointer"
onButtonReleaseEvent={() => {
takeScreenshot(`-w ${client.address}`);
}}
>
<box halign={Gtk.Align.CENTER}>
<icon icon={get_app_icon(Applications.fuzzy_query(client.class)[0])} />
<Separator size={ICON_SEP} />
<label
label={client.title}
truncate
max_width_chars={50}
/>
</box>
</button>
));
};
Hyprland.connect('notify::clients', updateWindows);
Hyprland.connect('notify::focused-workspace', updateWindows);
const Shown = Variable<string>('monitors');
const stack = (
<stack
shown={bind(Shown)}
transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT}
>
<scrollable name="monitors">
<box vertical>
{bind(Hyprland, 'monitors').as((monitors) => monitors.map((monitor) => (
<button
className="item-btn"
cursor="pointer"
onButtonReleaseEvent={() => {
takeScreenshot(`-o ${monitor.name}`);
}}
>
<label
label={`${monitor.name}: ${monitor.description}`}
truncate
maxWidthChars={50}
/>
</button>
)))}
</box>
</scrollable>
<scrollable name="windows">
{windowList}
</scrollable>
</stack>
) as Widget.Stack;
const StackButton = ({ label = '', iconName = '' }) => (
<button
cursor="pointer"
className={bind(Shown).as((shown) =>
`header-btn${shown === label ? ' active' : ''}`)}
onButtonReleaseEvent={() => {
Shown.set(label);
}}
>
<box halign={Gtk.Align.CENTER}>
<icon icon={iconName} />
<Separator size={ICON_SEP} />
{label}
</box>
</button>
) as Widget.Button;
const regionButton = (
<button
cursor="pointer"
className="header-btn"
onButtonReleaseEvent={() => {
takeScreenshot('-g "$(slurp)"', 0);
}}
>
<box halign={Gtk.Align.CENTER}>
<icon icon="tool-pencil-symbolic" />
<Separator size={ICON_SEP} />
region
</box>
</button>
) as Widget.Button;
return (
<PopupWindow
name="screenshot"
on_open={() => {
updateWindows();
}}
>
<box
className="screenshot widget"
vertical
>
<box
className="header"
homogeneous
>
<StackButton label="monitors" iconName="display-symbolic" />
<StackButton label="windows" iconName="window-symbolic" />
{regionButton}
</box>
{stack}
</box>
</PopupWindow>
);
};

View file

@ -0,0 +1,25 @@
.screenshot {
font-size: 30px;
.header {
.header-btn {
margin: 5px;
transition: background 400ms;
&.active {
background: $window_bg_color;
}
}
}
scrollable {
margin: 5px;
min-height: 400px;
box {
.item-btn {
margin: 3px;
}
}
}
}