Skip to content
Open
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
1 change: 1 addition & 0 deletions Binner/Binner.Web/ClientApp/src/common/UserTokenType.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const UserTokenType = {
KiCadApiToken: { value: 5, name: 'KiCad Api', icon: 'microchip', description: 'Link KiCad to Binner using an HTTP Library' },
BinnerBinApiToken: { value: 6, name: 'Binner Bin Api', icon: 'microchip', description: 'Link a Binner Bin' },
};
80 changes: 79 additions & 1 deletion Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useMemo } from "react";
import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { useNavigate, useLocation, useSearchParams } from "react-router-dom";
import { useTranslation, Trans } from "react-i18next";
import { createMedia } from "@artsy/fresnel";
Expand Down Expand Up @@ -69,6 +69,11 @@ export default function PartsGrid2Memoized({
return result;
};

const [showPopup, setShowPopup] = useState(false);
const [popupContent, setPopupContent] = useState(null);
const [partToLocate, setPartToLocate] = useState("");
let locationKeepaliveTimer = useRef(null);

const [_parts, setParts] = useState(parts);
const [_page, setPage] = useState(page);
const [pageSize, setPageSize] = useState(getViewPreference('pageSize') || 25);
Expand Down Expand Up @@ -130,6 +135,7 @@ export default function PartsGrid2Memoized({

useEffect(() => {
setDisabledPartIds(disabledPartIds);
stopLocatingPart();
}, [disabledPartIds]);

const handlePageChange = (e, control) => {
Expand All @@ -143,6 +149,70 @@ export default function PartsGrid2Memoized({
window.open(url, "_blank");
};

const locateKeepalive = async (part) => {
// update the location with our partnumber
return fetchApi(`/api/highlight/update?partNumber=${encodeURIComponent(part)}`, { method: "POST" });
}

const startLocatingPart = async (e, part) => {
e.preventDefault();
e.stopPropagation();

// get the partnumber we can use to locate the part
const p = part.partNumber.trim();

// set the part to locate for the modal
setPartToLocate(p);

// clear any keepalives
if (locationKeepaliveTimer.current) {
clearInterval(locationKeepaliveTimer.current);
}

// do a keepalive straight away to check if we have a good
// response and for the color
const res = await locateKeepalive(p);

// check for a 200 response
if (res?.responseObject?.status === 200 && res?.data?.color !== null) {
// send a locate to the server every second
locationKeepaliveTimer.current = setInterval(() => {
locateKeepalive(p);
}, 2500);

// get the color we need to show
const result = res.data.color;

// show the popup
setPopupContent({
title: "Part Location",
footerColor: result,
message: (
<>
<p>Part is now highlighted in <strong>{result}</strong>.</p>
<p>Press close to stop highlighting <strong>{part.partNumber}</strong></p>
</>
)
});
setShowPopup(true);
}
};

const stopLocatingPart = async () => {
if (locationKeepaliveTimer.current) {
clearInterval(locationKeepaliveTimer.current);
locationKeepaliveTimer.current = null;

// stop the highlighting straight away when we cancel
if (partToLocate !== undefined) {
fetchApi(`/api/highlight/stop?partNumber=${encodeURIComponent(partToLocate)}`, { method: "POST" });
}

setShowPopup(false);
setPartToLocate("")
}
}

const handlePrintLabel = async (e, part) => {
e.preventDefault();
e.stopPropagation();
Expand Down Expand Up @@ -344,6 +414,7 @@ export default function PartsGrid2Memoized({
case 'actions':
return {...def, Header: <i key={key}></i>, columnDefType: 'display', Cell: ({row}) => (
<>
{<Button circular size='mini' icon='location arrow' title='Locate' onClick={e => startLocatingPart(e, row.original)} />}
{columnsArray.includes('datasheetUrl') && columnsVisibleArray.includes('datasheetUrl') && <Button circular size='mini' icon='file pdf outline' title='View PDF' onClick={e => handleVisitLink(e, row.original.datasheetUrl)} />}
{columnsArray.includes('print') && columnsVisibleArray.includes('print') && <Button circular size='mini' icon='print' title='Print Label' onClick={e => handlePrintLabel(e, row.original)} />}
{columnsArray.includes('delete') && columnsVisibleArray.includes('delete') && <Button circular size='mini' icon='delete' title='Delete part' onClick={e => confirmDeleteOpen(e, row.original)} />}
Expand Down Expand Up @@ -489,6 +560,13 @@ export default function PartsGrid2Memoized({
<Button onClick={handleModalClose}>{"comp.partsGrid.ok"}</Button>
</Modal.Actions>
</Modal>
<Modal open={showPopup} onCancel={() => stopLocatingPart()} onClose={() => stopLocatingPart()}>
<Modal.Header>{popupContent?.title}</Modal.Header>
<Modal.Content>{popupContent?.message}</Modal.Content>
<Modal.Actions style={{ backgroundColor: popupContent?.footerColor }}>
<Button onClick={() => stopLocatingPart()}>{t('button.close', "Close")}</Button>
</Modal.Actions>
</Modal>
</div>
);
}
Expand Down
83 changes: 72 additions & 11 deletions Binner/Binner.Web/ClientApp/src/components/modals/AddTokenModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ export function AddTokenModal({ isOpen = false, onAdd, onClose, ...rest }) {
const defaultForm = {
tokenType: "",
partsTimeout: 5,
categoriesTimeout: 10
categoriesTimeout: 10,
location: "",
binNumber: ""
};
const [_isOpen, setIsOpen] = useState(false);
const [form, setForm] = useState(defaultForm);

const tokenOptions = [
{ key: 1, text: GetTypeName(UserTokenType, UserTokenType.KiCadApiToken.value), description: GetTypeProperty(UserTokenType, UserTokenType.KiCadApiToken.value, "description"), value: UserTokenType.KiCadApiToken.value },
{ key: 2, text: GetTypeName(UserTokenType, UserTokenType.BinnerBinApiToken.value), description: GetTypeProperty(UserTokenType, UserTokenType.BinnerBinApiToken.value, "description"), value: UserTokenType.BinnerBinApiToken.value },
];

useEffect(() => {
Expand All @@ -43,17 +46,44 @@ export function AddTokenModal({ isOpen = false, onAdd, onClose, ...rest }) {
};

const handleAdd = (e) => {
const data = {
tokenType: form.tokenType,
tokenConfig: JSON.stringify({
timeout_parts_seconds: parseInt(form.partsTimeout),
timeout_categories_seconds: parseInt(form.categoriesTimeout),
})
if (!onAdd) {
console.error("No onAdd handler defined!");
return;
}
if (onAdd) {
onAdd(e, data);
} else {
console.error("No onAdd handler defined!");

switch (form.tokenType) {
case UserTokenType.BinnerBinApiToken.value:
{
// make sure we have all the required parameters
if (form.binNumber.length === 0 || form.location.length === 0) {
return null;
}

const data = {
tokenType: form.tokenType,
tokenConfig: JSON.stringify({
location: form.location,
binNumber: form.binNumber
})
}
onAdd(e, data);
}
break;
case UserTokenType.KiCadApiToken.value:
{
const data = {
tokenType: form.tokenType,
tokenConfig: JSON.stringify({
timeout_parts_seconds: parseInt(form.partsTimeout),
timeout_categories_seconds: parseInt(form.categoriesTimeout),
})
}
onAdd(e, data);
}
break;
default:
console.error("No onAdd handler defined for tokenType!");
break;
}
};

Expand Down Expand Up @@ -88,6 +118,37 @@ export function AddTokenModal({ isOpen = false, onAdd, onClose, ...rest }) {
</Form.Field>
</div>
);
case UserTokenType.BinnerBinApiToken.value:
return (
<div style={{padding: '20px'}}>
<Form.Field>
<Popup
content={<p>Enter the location of the Binner Bin</p>}
trigger={<Form.Input
label="Location"
placeholder=""
required
value={form.location || ''}
name="location"
onChange={handleChange}
/>}
/>
</Form.Field>
<Form.Field>
<Popup
content={<p>Enter the bin number of the Binner Bin</p>}
trigger={<Form.Input
label="Bin Number"
placeholder=""
required
value={form.binNumber || ''}
name="binNumber"
onChange={handleChange}
/>}
/>
</Form.Field>
</div>
);
}
return (<></>);
};
Expand Down
21 changes: 17 additions & 4 deletions Binner/Binner.Web/ClientApp/src/pages/Account.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,10 @@ export function Account(props) {
}).then((response) => {
if (response.responseObject.ok) {
const { data } = response;
// remove any tokens of the same type, only 1 allowed per user
account.tokens = _.filter(account.tokens, (item) => item.tokenType !== request.tokenType);
// remove any tokens of the same type for KiCad, only 1 allowed per user
if (data.tokenType === UserTokenType.KiCadApiToken.value) {
account.tokens = _.filter(account.tokens, (item) => item.tokenType !== request.tokenType);
}
account.tokens.push(data);
setAccount(account);
toast.success(t("success.tokenCreated", "Token created!"));
Expand Down Expand Up @@ -407,10 +409,10 @@ export function Account(props) {
<Table.Row key={key}>
<Table.Cell>{GetTypeName(UserTokenType, token.tokenType)}</Table.Cell>
<Table.Cell>
<div className="token" name="token">{token.value}</div>
<div className="token" name={token.value}>{token.value}</div>
<div style={{float: 'right'}}>
<Clipboard text={token.value} style={{marginRight: '10px'}} />
<Hide element="token" />
<Hide element={token.value} />
</div>
</Table.Cell>
<Table.Cell>{format(parseJSON(token.dateCreatedUtc), FormatShortDate)}</Table.Cell>
Expand All @@ -423,6 +425,17 @@ export function Account(props) {
trigger={<Link to={`/api/download/kicad?token=${token.value}`} onClick={e => handleDownloadKiCadToken(e, token.value)}><Icon name="download" /> Download Config</Link>}
/>
}

{token.tokenType === UserTokenType.BinnerBinApiToken.value &&
<Popup
wide
content={<p>Binner bin location (bin number)</p>}
trigger={
<p style={{ cursor: "pointer", color: "#4183c4" }}>
📌 {JSON.parse(token.tokenConfig)?.location} (📦 {JSON.parse(token.tokenConfig)?.binNumber})
</p>}
/>
}
</Table.Cell>
<Table.Cell textAlign="center">
<Button
Expand Down
1 change: 1 addition & 0 deletions Binner/Binner.Web/Configuration/StartupConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public static IConfigurationRoot Configure(IServiceCollection services)
services.AddSingleton(authenticationConfiguration);
services.AddSingleton(storageProviderConfiguration);
services.AddSingleton(binnerConfig);
services.AddHostedService<Controllers.RefCleanupService>();

return configuration;
}
Expand Down
6 changes: 4 additions & 2 deletions Binner/Binner.Web/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public async Task<IActionResult> CreateTokenAsync(CreateTokenRequest request)
switch(request.TokenType)
{
case TokenTypes.KiCadApiToken:
var token = await _accountService.CreateKiCadApiTokenAsync(request.TokenConfig);
case TokenTypes.BinnerBinApiToken:
var token = await _accountService.CreateApiTokenAsync(request.TokenType, request.TokenConfig);
return Ok(token);
default:
return BadRequest("Unsupported token type");
Expand All @@ -73,7 +74,8 @@ public async Task<IActionResult> DeleteTokenAsync(DeleteTokenRequest request)
switch (request.TokenType)
{
case TokenTypes.KiCadApiToken:
var token = await _accountService.DeleteKiCadApiTokenAsync(request.Value);
case TokenTypes.BinnerBinApiToken:
var token = await _accountService.DeleteApiTokenAsync(request.TokenType, request.Value);
return Ok(token);
default:
return BadRequest("Unsupported token type");
Expand Down
Loading