mirror of
https://gitee.com/ineo6/homebrew-install.git
synced 2025-06-24 21:39:15 +08:00
feat: 添加app模块
This commit is contained in:
parent
39ff6f1b8d
commit
5f112390b5
331
.dumi/theme/builtins/AppDetail.tsx
Normal file
331
.dumi/theme/builtins/AppDetail.tsx
Normal file
@ -0,0 +1,331 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Select, Input, Table } from 'antd';
|
||||
import queryString from 'query-string';
|
||||
// @ts-ignore
|
||||
import SourceCode from 'dumi-theme-default/src/builtins/SourceCode';
|
||||
// @ts-ignore
|
||||
import Alert from 'dumi-theme-default/src/builtins/Alert';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import './CaskAndFormula/app-detail.less';
|
||||
|
||||
const Option = Select.Option;
|
||||
const { Search } = Input;
|
||||
|
||||
const getQuery = () => {
|
||||
return queryString.parse(window.location.search);
|
||||
};
|
||||
|
||||
interface DataType {
|
||||
arch: string;
|
||||
os: string;
|
||||
version: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
title: 'arch',
|
||||
dataIndex: 'arch',
|
||||
width: 200,
|
||||
onCell: (_, index) => {
|
||||
if (index === 0) {
|
||||
return { rowSpan: 10 };
|
||||
}
|
||||
|
||||
return { rowSpan: 0 };
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'os',
|
||||
dataIndex: 'os',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: 'version',
|
||||
dataIndex: 'version',
|
||||
render: (text, row) => {
|
||||
return (
|
||||
<a href={row.url} target="_blank" rel="noopener noreferrer">
|
||||
{row.version}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const AppDetail = ({}) => {
|
||||
const [caskDetail, setCaskDetail] = useState({});
|
||||
const [formulaDetail, setFormulaDetail] = useState({});
|
||||
const [isCask, setIsCask] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const query = getQuery();
|
||||
|
||||
if (query.cid) {
|
||||
getCaskDetail(query.cid);
|
||||
setIsCask(true);
|
||||
} else if (query.fid) {
|
||||
getFormulaDetail(query.fid);
|
||||
setIsCask(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getCaskDetail = id => {
|
||||
fetch(
|
||||
'http://localhost:8000/homebrew/cask-detail?' +
|
||||
queryString.stringify({
|
||||
id,
|
||||
}),
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(res => {
|
||||
if (res.code === 0 && res.result) {
|
||||
const result = res.result;
|
||||
|
||||
result.variations = JSON.parse(result.variations);
|
||||
result.depends_on = JSON.parse(result.depends_on);
|
||||
|
||||
setCaskDetail(result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getFormulaDetail = id => {
|
||||
fetch(
|
||||
'http://localhost:8000/homebrew/formula-detail?' +
|
||||
queryString.stringify({
|
||||
id,
|
||||
}),
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(res => {
|
||||
if (res.code === 0 && res.result) {
|
||||
const result = res.result;
|
||||
|
||||
// result.versions = JSON.parse(result.versions);
|
||||
// result.versioned_formulae = JSON.parse(result.versioned_formulae);
|
||||
// result.dependencies = JSON.parse(result.dependencies);
|
||||
// result.build_dependencies = JSON.parse(result.build_dependencies);
|
||||
|
||||
console.log(result);
|
||||
setFormulaDetail(result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const renderObjectJson = obj => {
|
||||
if (!obj) return;
|
||||
|
||||
const list: any = [];
|
||||
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const element = obj[key];
|
||||
|
||||
for (const condition in element) {
|
||||
if (Object.prototype.hasOwnProperty.call(element, condition)) {
|
||||
const version = element[condition];
|
||||
|
||||
list.push(
|
||||
<div>
|
||||
<p>
|
||||
{key}
|
||||
{condition}
|
||||
{version}
|
||||
</p>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (list.length === 0) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="app-detail-content-title">系统要求</h3>
|
||||
{list}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderVariations = variations => {
|
||||
console.log(variations);
|
||||
if (!variations) return;
|
||||
|
||||
const keys = Object.keys(variations);
|
||||
|
||||
if (keys.length === 0) return;
|
||||
|
||||
const intelDataArray: DataType[] = [];
|
||||
const appleSiliconDataArray: any = [];
|
||||
|
||||
keys.forEach(key => {
|
||||
const value = variations[key];
|
||||
if (key.includes('arm64_')) {
|
||||
appleSiliconDataArray.push({
|
||||
arch: 'Apple Silicon',
|
||||
os: key.replace('arm64_', ''),
|
||||
url: value.url,
|
||||
version: value.version || caskDetail.version,
|
||||
});
|
||||
} else {
|
||||
intelDataArray.push({
|
||||
arch: 'Intel',
|
||||
os: key,
|
||||
url: value.url,
|
||||
version: value.version || caskDetail.version,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="app-detail-content-title">软件版本</h3>
|
||||
<div className="variations-content">
|
||||
{intelDataArray.length > 0 && (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={intelDataArray}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
bordered
|
||||
/>
|
||||
)}
|
||||
|
||||
{appleSiliconDataArray.length > 0 && (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={appleSiliconDataArray}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
bordered
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderCask = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="app-img">
|
||||
<span>
|
||||
<h1>
|
||||
{caskDetail.token} {caskDetail.version}
|
||||
</h1>
|
||||
<h2>{caskDetail.desc}</h2>
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="app-detail-content-title">安装命令</h3>
|
||||
|
||||
<SourceCode
|
||||
code={`brew install --cask ${caskDetail.token}`}
|
||||
lang="bash"
|
||||
/>
|
||||
|
||||
<div>{renderObjectJson(caskDetail.depends_on)}</div>
|
||||
|
||||
{caskDetail.caveats && <Alert type="info">{caskDetail.caveats}</Alert>}
|
||||
|
||||
{renderVariations(caskDetail.variations)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderVersionedFormulae = versionedFormulae => {
|
||||
if (!versionedFormulae) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="app-detail-content-title">其他版本</h3>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'name',
|
||||
render: (text, row) => {
|
||||
return <div>{text}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'versions',
|
||||
render: (text, row) => {
|
||||
return <div>{text.stable}</div>;
|
||||
},
|
||||
},
|
||||
]}
|
||||
dataSource={versionedFormulae}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
bordered
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderDep = (deps, title) => {
|
||||
if (!deps) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="app-detail-content-title">{title}</h3>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
dataIndex: 'name',
|
||||
render: (text, row) => {
|
||||
return <div>{text}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'versions',
|
||||
render: (text) => {
|
||||
return <div>{text.stable}</div>;
|
||||
},
|
||||
},
|
||||
]}
|
||||
dataSource={deps}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
bordered
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFormula = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="app-img">
|
||||
<span>
|
||||
<h1>
|
||||
{formulaDetail.name} {formulaDetail.versions?.stable}
|
||||
</h1>
|
||||
<h2>{formulaDetail.desc}</h2>
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="app-detail-content-title">安装命令</h3>
|
||||
|
||||
<SourceCode code={`brew install ${formulaDetail.name}`} lang="bash" />
|
||||
|
||||
{renderVersionedFormulae(formulaDetail.versioned_formulae)}
|
||||
|
||||
{renderDep(formulaDetail.dependencies, '依赖')}
|
||||
|
||||
{renderDep(formulaDetail.build_dependencies, '构建依赖')}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="__dumi-default-app-detail">
|
||||
<div className="app-detail-content">
|
||||
{isCask ? renderCask() : renderFormula()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppDetail;
|
205
.dumi/theme/builtins/CaskAndFormula.tsx
Normal file
205
.dumi/theme/builtins/CaskAndFormula.tsx
Normal file
@ -0,0 +1,205 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Select, Tooltip, Checkbox } from 'antd';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
// @ts-ignore
|
||||
import SourceCode from 'dumi-theme-default/src/builtins/SourceCode';
|
||||
|
||||
import './SourceGenerate.less';
|
||||
import CaskCard from './CaskAndFormula/Cask';
|
||||
import { Card, List, Input, Tabs } from 'antd';
|
||||
import queryString from 'query-string';
|
||||
|
||||
const Option = Select.Option;
|
||||
const { Search } = Input;
|
||||
|
||||
const CaskAndFormula = ({}) => {
|
||||
const [listParams, setListParams] = useState({
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
});
|
||||
const [activeKey, setActiveKey] = useState('cask');
|
||||
|
||||
const [caskPageData, setCaskPageData] = useState({
|
||||
data: [],
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const [formulaPageData, setFormulaPageData] = useState({
|
||||
data: [],
|
||||
total: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getList();
|
||||
}, [activeKey, listParams]);
|
||||
|
||||
const getList = () => {
|
||||
if (activeKey === 'cask') {
|
||||
getCaskList();
|
||||
} else {
|
||||
getFormula();
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (key: string) => {
|
||||
setActiveKey(key);
|
||||
};
|
||||
|
||||
const getCaskList = () => {
|
||||
fetch(
|
||||
'http://localhost:8000/homebrew/cask-list?' +
|
||||
queryString.stringify(listParams),
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(res => {
|
||||
if (res.code === 0 && res.result) {
|
||||
setCaskPageData(res.result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getFormula = () => {
|
||||
fetch(
|
||||
'http://localhost:8000/homebrew/formula-list?' +
|
||||
queryString.stringify(listParams),
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(res => {
|
||||
if (res.code === 0 && res.result) {
|
||||
const result = res.result
|
||||
|
||||
if (result && result.data) {
|
||||
result.data.forEach(item => {
|
||||
if (item.versions) {
|
||||
item.versions = JSON.parse(item.versions)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setFormulaPageData(result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const openCaskDetail = item => {
|
||||
window.open('/app-detail?cid=' + item.id);
|
||||
};
|
||||
|
||||
const openFormulaDetail = item => {
|
||||
window.open('/app-detail?fid=' + item.id);
|
||||
};
|
||||
|
||||
const onSearch = value => {
|
||||
setListParams({
|
||||
...listParams,
|
||||
page: 1,
|
||||
search: value,
|
||||
});
|
||||
|
||||
getList();
|
||||
};
|
||||
|
||||
const renderCask = () => {
|
||||
return (
|
||||
<List
|
||||
grid={{ gutter: 16, column: 4 }}
|
||||
dataSource={caskPageData.data}
|
||||
pagination={{
|
||||
onChange: page => {
|
||||
console.log(page);
|
||||
setListParams({
|
||||
...listParams,
|
||||
page,
|
||||
});
|
||||
},
|
||||
pageSize: listParams.pageSize,
|
||||
total: caskPageData.total,
|
||||
}}
|
||||
renderItem={item => (
|
||||
<List.Item>
|
||||
<Card
|
||||
title={item.token}
|
||||
extra={<a onClick={openCaskDetail.bind(this, item)}>查看</a>}
|
||||
>
|
||||
<div>{item.desc}</div>
|
||||
<div>
|
||||
<div style={{ marginTop: '10px', color: '#999' }}>
|
||||
{item.version}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFormula = () => {
|
||||
return (
|
||||
<List
|
||||
grid={{ gutter: 16, column: 4 }}
|
||||
dataSource={formulaPageData.data}
|
||||
pagination={{
|
||||
onChange: page => {
|
||||
setListParams({
|
||||
...listParams,
|
||||
page,
|
||||
});
|
||||
},
|
||||
pageSize: listParams.pageSize,
|
||||
total: formulaPageData.total,
|
||||
}}
|
||||
renderItem={item => (
|
||||
<List.Item>
|
||||
<Card
|
||||
title={item.name}
|
||||
extra={<a onClick={openFormulaDetail.bind(this, item)}>查看</a>}
|
||||
>
|
||||
<div>{item.desc}</div>
|
||||
<div>
|
||||
<div style={{ marginTop: '10px', color: '#999' }}>
|
||||
{item.versions.stable}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="__dumi-default-cask-and-formula">
|
||||
<Tabs
|
||||
defaultActiveKey={activeKey}
|
||||
onChange={onChange}
|
||||
size="large"
|
||||
items={[
|
||||
{
|
||||
key: 'cask',
|
||||
label: 'Cask',
|
||||
},
|
||||
{
|
||||
key: 'formula',
|
||||
label: 'Formula',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Search
|
||||
placeholder="搜索..."
|
||||
allowClear
|
||||
enterButton="Search"
|
||||
size="large"
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
{activeKey === 'cask' ? renderCask() : renderFormula()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaskAndFormula;
|
17
.dumi/theme/builtins/CaskAndFormula/Cask.tsx
Normal file
17
.dumi/theme/builtins/CaskAndFormula/Cask.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Select, Tooltip, Checkbox } from 'antd';
|
||||
|
||||
const Option = Select.Option;
|
||||
|
||||
const CaskCard = ({ data }) => {
|
||||
const [terminalType, setTerminalType] = useState<string>('zsh');
|
||||
const [isJsonApi, setJsonApi] = useState<boolean>(true);
|
||||
|
||||
return (
|
||||
<div className="__dumi-default-cask-card">
|
||||
{data.token}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CaskCard;
|
38
.dumi/theme/builtins/CaskAndFormula/app-detail.less
Normal file
38
.dumi/theme/builtins/CaskAndFormula/app-detail.less
Normal file
@ -0,0 +1,38 @@
|
||||
.__dumi-default-app-detail {
|
||||
.app-detail-content {
|
||||
background: #fff;
|
||||
max-width: 780px;
|
||||
margin: 20px 0 0;
|
||||
padding: 20px 30px 25px;
|
||||
border-radius: 5px;
|
||||
|
||||
.app-img {
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #999;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.variations-content {
|
||||
.ant-table-wrapper + .ant-table-wrapper {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
137
.dumi/theme/style/flex.less
Normal file
137
.dumi/theme/style/flex.less
Normal file
@ -0,0 +1,137 @@
|
||||
//from tailwindcss
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flex-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.items-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.items-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.content-center {
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.content-start {
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.content-end {
|
||||
align-content: flex-end;
|
||||
}
|
||||
|
||||
.content-between {
|
||||
align-content: space-between;
|
||||
}
|
||||
|
||||
.content-around {
|
||||
align-content: space-around;
|
||||
}
|
||||
|
||||
.self-auto {
|
||||
align-self: auto;
|
||||
}
|
||||
|
||||
.self-start {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.self-end {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.self-center {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.self-stretch {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.justify-evenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.flex-initial {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.flex-none {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.flex-grow-0 {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
@import './markdown.less';
|
||||
@import './variables.less';
|
||||
@import './flex.less';
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
8
docs/app-detail.md
Normal file
8
docs/app-detail.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
nav:
|
||||
hide: true
|
||||
toc: false
|
||||
sidemenu: false
|
||||
---
|
||||
|
||||
<AppDetail></AppDetail>
|
12
docs/homebrew.md
Normal file
12
docs/homebrew.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
nav:
|
||||
title: 应用
|
||||
toc: false
|
||||
sidemenu: false
|
||||
---
|
||||
|
||||
# Cask && Formula
|
||||
|
||||
如果我的工作帮助到你,您可以考虑请我喝杯咖啡。
|
||||
|
||||
<CaskAndFormula></CaskAndFormula>
|
@ -47,7 +47,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.5.0",
|
||||
"antd": "^4.13.1"
|
||||
"antd": "^4.24.16",
|
||||
"query-string": "^9.1.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
|
Binary file not shown.
653
pnpm-lock.yaml
generated
653
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user