improve submenus & style

This commit is contained in:
srgooglo 2023-11-15 16:59:13 +01:00
parent ad346ea55e
commit 43ee91cae7
9 changed files with 555 additions and 298 deletions

View File

@ -15,6 +15,9 @@
.manifest_info-icon {
width: 80px;
height: 80px;
min-width: 80px;
min-height: 80px;
max-height: 80px;
border-radius: 12px;

View File

@ -0,0 +1,36 @@
import React from "react"
import * as antd from "antd"
import { Context as InstallationsContext } from "contexts/installations"
import "./index.less"
const NewInstallation = (props) => {
const { install } = React.useContext(InstallationsContext)
const [manifestUrl, setManifestUrl] = React.useState("")
function handleClickInstall() {
install(manifestUrl)
props.close()
}
return <div className="new_installation_prompt">
<antd.Input
placeholder="Manifest URL"
value={manifestUrl}
onChange={(e) => {
setManifestUrl(e.target.value)
}}
onPressEnter={handleClickInstall}
/>
<antd.Button
type="primary"
onClick={handleClickInstall}
>
Install
</antd.Button>
</div>
}
export default NewInstallation

View File

@ -0,0 +1,9 @@
.new_installation_prompt {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
}

View File

@ -0,0 +1,181 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import BarLoader from "react-spinners/BarLoader"
import { MdFolder, MdDelete, MdPlayArrow, MdUpdate, MdOutlineMoreVert, MdSettings, MdInfoOutline } from "react-icons/md"
import "./index.less"
const PackageItem = (props) => {
const [manifest, setManifest] = React.useState(props.manifest)
const isLoading = manifest.status === "installing" || manifest.status === "uninstalling" || manifest.status === "updating"
const isInstalled = manifest.status === "installed"
const isFailed = manifest.status === "failed"
const onClickUpdate = () => {
antd.Modal.confirm({
title: "Update",
content: `Are you sure you want to update ${manifest.id}?`,
onOk: () => {
ipc.exec("bundle:update", manifest.id)
},
})
}
const onClickPlay = () => {
ipc.exec("bundle:exec", manifest.id)
}
const onClickFolder = () => {
ipc.exec("bundle:open", manifest.id)
}
const onClickDelete = () => {
antd.Modal.confirm({
title: "Uninstall",
content: `Are you sure you want to uninstall ${manifest.id}?`,
onOk: () => {
ipc.exec("bundle:uninstall", manifest.id)
},
})
}
function handleUpdate(event, data) {
setManifest({
...manifest,
...data,
})
}
function renderStatusLine(manifest) {
if (isLoading) {
return manifest.status
}
return `v${manifest.version}` ?? "N/A"
}
const MenuProps = {
items: [
{
key: "open_folder",
label: "Open Folder",
icon: <MdFolder />,
onClick: onClickFolder,
},
{
key: "update",
label: "Update",
icon: <MdUpdate />,
onClick: onClickUpdate,
},
{
key: "options",
label: "Options",
icon: <MdSettings />,
disabled: true
},
{
type: "divider"
},
{
key: "info",
label: "Info",
icon: <MdInfoOutline />,
disabled: true
},
{
key: "delete",
label: "Uninstall",
icon: <MdDelete />,
onClick: onClickDelete,
danger: true,
},
],
}
React.useEffect(() => {
ipc.on(`installation:${manifest.id}:status`, handleUpdate)
return () => {
ipc.off(`installation:${manifest.id}:status`, handleUpdate)
}
}, [])
React.useEffect(() => {
setManifest(props.manifest)
}, [props.manifest])
return <div
className={classnames(
"installation_item_wrapper",
{
["status_visible"]: !isInstalled
}
)}
>
<div className="installation_item">
<img src={manifest.icon} className="installation_item_icon" />
<div className="installation_item_info">
<h2>
{
manifest.pack_name
}
</h2>
<p>
{
renderStatusLine(manifest)
}
</p>
</div>
<div className="installation_item_actions">
{
isFailed && <antd.Button
type="primary"
>
Retry
</antd.Button>
}
{
isInstalled && manifest.executable && <antd.Dropdown.Button
menu={MenuProps}
onClick={onClickPlay}
type="primary"
trigger={["click"]}
>
<MdPlayArrow />
</antd.Dropdown.Button>
}
{
isInstalled && !manifest.executable && <antd.Dropdown
menu={MenuProps}
>
<antd.Button
icon={<MdOutlineMoreVert />}
type="primary"
/>
</antd.Dropdown>
}
</div>
</div>
<div
className="installation_status"
>
{
isLoading && <BarLoader color={getRootCssVar("--primary-color")} className="app_loader" />
}
<p>{manifest.statusText ?? "Unknown status"}</p>
</div>
</div>
}
export default PackageItem

View File

@ -0,0 +1,137 @@
@installation-item-borderRadius: 12px;
@installation-item-height: 60px;
.installation_item_wrapper {
position: relative;
display: flex;
flex-direction: column;
&.status_visible {
.installation_item {
border-bottom: 1px solid var(--border-color);
}
.installation_status {
height: fit-content;
padding: 10px 20px;
padding-top: calc(8px + 10px);
opacity: 1;
transform: translateY(-8px);
}
}
&:nth-child(odd) {
.installation_item {
background-color: var(--background-color-primary);
}
.installation_status {
background-color: var(--background-color-primary);
}
}
.installation_item {
display: flex;
flex-direction: row;
align-items: center;
height: @installation-item-height;
max-height: @installation-item-height;
min-height: @installation-item-height;
gap: 10px;
padding: 5px;
border-radius: @installation-item-borderRadius;
background-color: var(--background-color-primary);
z-index: 50;
.installation_item_info {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
h2 {
display: inline-flex;
flex-direction: row;
width: 100%;
}
p {
font-size: 0.7rem;
text-transform: uppercase;
}
}
.installation_item_icon {
width: 50px;
height: 50px;
min-width: 50px;
min-height: 50px;
background-color: var(--background-color-secondary);
overflow: hidden;
border-radius: 12px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.installation_item_actions {
display: flex;
gap: 10px;
align-items: center;
}
}
.installation_status {
position: relative;
z-index: 49;
display: inline-flex;
flex-direction: column;
background-color: var(--background-color-primary);
gap: 10px;
width: 100%;
border-radius: 0 0 12px 12px;
padding: 0;
margin: 0;
opacity: 0;
height: 0;
overflow: hidden;
p {
font-size: 0.7rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 14px;
}
}
}

View File

@ -1,176 +1,15 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import BarLoader from "react-spinners/BarLoader"
import { MdAdd, MdFolder, MdDelete, MdPlayArrow, MdUpdate } from "react-icons/md"
import { MdAdd } from "react-icons/md"
import { Context as InstallationsContext, WithContext } from "contexts/installations"
import PackageItem from "components/PackageItem"
import NewInstallation from "components/NewInstallation"
import "./index.less"
const NewInstallation = (props) => {
const { install } = React.useContext(InstallationsContext)
const [manifestUrl, setManifestUrl] = React.useState("")
function handleClickInstall() {
install(manifestUrl)
props.close()
}
return <div className="new_installation_prompt">
<antd.Input
placeholder="Manifest URL"
value={manifestUrl}
onChange={(e) => {
setManifestUrl(e.target.value)
}}
onPressEnter={handleClickInstall}
/>
<antd.Button
type="primary"
onClick={handleClickInstall}
>
Install
</antd.Button>
</div>
}
const InstallationItem = (props) => {
const [manifest, setManifest] = React.useState(props.manifest)
const isLoading = manifest.status === "installing" || manifest.status === "uninstalling" || manifest.status === "updating"
const isInstalled = manifest.status === "installed"
const isFailed = manifest.status === "failed"
const onClickUpdate = () => {
ipc.exec("bundle:update", manifest.id)
}
const onClickPlay = () => {
ipc.exec("bundle:exec", manifest.id)
}
const onClickFolder = () => {
ipc.exec("bundle:open", manifest.id)
}
const onClickDelete = () => {
ipc.exec("bundle:uninstall", manifest.id)
}
function handleUpdate(event, data) {
setManifest({
...manifest,
...data,
})
}
function renderStatusLine(manifest) {
if (isLoading) {
return manifest.status
}
return `v${manifest.version}` ?? "N/A"
}
React.useEffect(() => {
ipc.on(`installation:${manifest.id}:status`, handleUpdate)
return () => {
ipc.off(`installation:${manifest.id}:status`, handleUpdate)
}
}, [])
React.useEffect(() => {
setManifest(props.manifest)
}, [props.manifest])
return <div
className={classnames(
"installation_item_wrapper",
{
["status_visible"]: !isInstalled
}
)}
>
<div className="installation_item">
<img src={manifest.icon} className="installation_item_icon" />
<div className="installation_item_info">
<h2>
{
manifest.pack_name
}
</h2>
<p>
{
renderStatusLine(manifest)
}
</p>
</div>
<div className="installation_item_actions">
{
isInstalled && manifest.executable && <antd.Button
type="primary"
icon={<MdPlayArrow />}
onClick={onClickPlay}
/>
}
{
isFailed && <antd.Button
type="primary"
>
Retry
</antd.Button>
}
{
isInstalled && <antd.Button
type="primary"
icon={<MdUpdate />}
onClick={onClickUpdate}
/>
}
{
isInstalled && <antd.Button
type="primary"
icon={<MdFolder />}
onClick={onClickFolder}
/>
}
{
isInstalled && <antd.Popconfirm
title="Delete Installation"
onConfirm={onClickDelete}
>
<antd.Button
type="ghost"
icon={<MdDelete />}
/>
</antd.Popconfirm>
}
</div>
</div>
<div
className="installation_status"
>
{
isLoading && <BarLoader color={getRootCssVar("--primary-color")} className="app_loader" />
}
<p>{manifest.statusText ?? "Unknown status"}</p>
</div>
</div>
}
class InstallationsManager extends React.Component {
static contextType = InstallationsContext
@ -205,7 +44,7 @@ class InstallationsManager extends React.Component {
{
installations.map((manifest) => {
return <InstallationItem key={manifest.id} manifest={manifest} />
return <PackageItem key={manifest.id} manifest={manifest} />
})
}
</div>

View File

@ -23,135 +23,4 @@
justify-content: center;
}
}
}
@installation-item-borderRadius: 12px;
.installation_item_wrapper {
position: relative;
display: flex;
flex-direction: column;
&.status_visible {
.installation_item {
border-bottom: 1px solid var(--border-color);
}
.installation_status {
height: fit-content;
padding: 10px 20px;
padding-top: calc(8px + 10px);
opacity: 1;
transform: translateY(-8px);
}
}
&:nth-child(odd) {
.installation_item {
background-color: var(--background-color-primary);
}
.installation_status {
background-color: var(--background-color-primary);
}
}
.installation_item {
display: flex;
flex-direction: row;
gap: 20px;
padding: 5px;
border-radius: @installation-item-borderRadius;
background-color: var(--background-color-primary);
z-index: 50;
.installation_item_info {
display: flex;
flex-direction: column;
gap: 10px;
p {
font-size: 0.7rem;
text-transform: uppercase;
}
}
.installation_item_icon {
width: 50px;
height: 50px;
min-width: 50px;
min-height: 50px;
overflow: hidden;
border-radius: 12px;
img {
width: 100%;
height: 100%;
}
}
.installation_item_actions {
display: flex;
width: 100%;
gap: 10px;
align-items: center;
justify-content: flex-end;
}
}
.installation_status {
position: relative;
z-index: 49;
display: inline-flex;
flex-direction: column;
background-color: var(--background-color-primary);
gap: 10px;
width: 100%;
border-radius: 0 0 12px 12px;
padding: 0;
margin: 0;
opacity: 0;
height: 0;
overflow: hidden;
p {
font-size: 0.7rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 14px;
}
}
}
.new_installation_prompt {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
}

View File

@ -0,0 +1,181 @@
.ant-select-dropdown {
.ant-select-item-option:not(.ant-select-item-option-disabled) {
background-color: var(--background-color-primary) !important;
color: var(--text-color) !important;
}
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
background-color: var(--background-color-secondary) !important;
color: var(--text-color) !important;
}
}
.ant-dropdown-menu {
.ant-dropdown-menu-item-divider {
background-color: var(--background-color-primary) !important;
}
.ant-dropdown-menu-item-disabled {
opacity: 0.4;
svg {
color: var(--text-color);
}
}
}
.ant-modal-content {
background-color: var(--background-color-primary) !important;
p,
span,
svg {
color: var(--text-color) !important;
}
.ant-modal-confirm-content {
color: var(--text-color) !important;
}
.ant-btn {
display: inline-flex;
align-items: center;
justify-content: center;
svg {
margin: 0 !important;
}
gap: 10px;
span {
margin: 0;
line-height: 1rem;
}
}
.ant-btn-primary:not(.ant-btn-dangerous) {
svg {
color: var(--background-color-primary) !important;
}
span {
color: var(--background-color-primary) !important;
margin: 0;
}
background-color: var(--primary-color);
&:hover {
background-color: var(--primary-color);
}
}
.ant-btn-primary:not(:disabled):not(.ant-btn-disabled) {
&:hover {
background-color: var(--primary-color);
filter: brightness(130%);
}
}
.ant-btn-primary[disabled] {
opacity: 0.5 !important;
svg {
color: unset !important;
}
span {
color: unset !important;
margin: 0;
}
}
.ant-btn[disabled] {
opacity: 0.5 !important;
}
.ant-btn-default {
&:not(.ant-btn-dangerous) {
border-color: var(--background-color-secondary) !important;
&:hover {
border-color: var(--background-color-primary) !important;
}
&:active {
border-color: var(--background-color-primary) !important;
}
&:focus {
border-color: var(--background-color-primary) !important;
}
}
color: var(--text-color);
background: var(--background-color-primary);
&:hover {
color: var(--text-color);
background: var(--background-color-secondary);
}
&:active {
color: var(--text-color);
background: var(--background-color-secondary);
}
&:focus {
color: var(--text-color);
background: var(--background-color-secondary);
}
}
.ant-btn-dangerous {
span {
color: var(--ant-error-color);
}
}
.ant-btn[disabled],
.ant-btn[disabled]:hover,
.ant-btn[disabled]:focus,
.ant-btn[disabled]:active {
background-color: var(--background-color-accent);
}
}
.ant-notification-notice {
background-color: var(--background-color-primary) !important;
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
color: var(--text-color) !important;
}
.ant-notification-notice-message,
.ant-notification-notice-description {
color: var(--text-color) !important;
}
}
.ant-message-notice-content {
background-color: var(--background-color-primary) !important;
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
color: var(--text-color) !important;
}
}

View File

@ -1,4 +1,5 @@
@import "style/reset.css";
@import "style/fix.less";
@var-text-color: #fff;
@var-background-color-primary: #424549;
@ -9,6 +10,7 @@
:root {
--background-color-primary: @var-background-color-primary;
--background-color-secondary: @var-background-color-secondary;
--primary-color: @var-primary-color;
--text-color: @var-text-color;
--border-color: @var-border-color;
@ -78,7 +80,7 @@ body {
.menu {
display: flex;
flex-direction: row;
flex-direction: row;
width: 100%;