forked from M-Labs/web2019
Compare commits
8 Commits
2a442064f2
...
2ecdeab416
Author | SHA1 | Date |
---|---|---|
|
2ecdeab416 | |
|
f532008dfc | |
|
a96b4bffab | |
|
6a45e382f7 | |
|
482888d617 | |
|
682629b821 | |
|
d59cbf4fb6 | |
|
c63249e8a0 |
14
README.md
14
README.md
|
@ -12,12 +12,14 @@ Clone the project:
|
|||
|
||||
Install Zola.
|
||||
|
||||
Start:
|
||||
Start with targeting `m-labs.hk` domain:
|
||||
|
||||
```
|
||||
zola serve
|
||||
```
|
||||
|
||||
Environmental variable `DOMAINNAME` can be specified to [substitute links on the website](#domain-handling).
|
||||
|
||||
To build the .bundle.js from .jsx files:
|
||||
|
||||
```
|
||||
|
@ -27,4 +29,12 @@ To build the .bundle.js from .jsx files:
|
|||
```
|
||||
|
||||
Development builds are also available. `npm run build-dev` to re/build once, or `npm run start-dev` for incremental
|
||||
continuous builds as source files change.
|
||||
continuous builds as source files change.
|
||||
|
||||
## Domain handling
|
||||
|
||||
Environmental variable `DOMAINNAME` controls only the following links on the website:
|
||||
* email `mailto:` links
|
||||
* hook for the RFQ server (`window.API_RFQ`) variable
|
||||
|
||||
If absent, `DOMAINNAME` defaults to `m-labs.hk`.
|
||||
|
|
|
@ -18,4 +18,4 @@ Selling drinks is not our main business and only a service we do to other Club M
|
|||
* In case of any dispute, the seller is always right.
|
||||
|
||||
|
||||
Contact: [sb@m-labs.hk](mailto:sb@m-labs.hk)
|
||||
Contact: {{ email(address="sb") }}
|
||||
|
|
|
@ -83,5 +83,5 @@ Now is your chance to work on top-notch science and technology projects that get
|
|||
|
||||
|
||||
{% layout_centered_content() %}
|
||||
##### Contact us at [jobs@m-labs.hk](mailto:jobs@m-labs.hk) or [jobs@m-labs.ph](mailto:jobs@m-labs.ph)!
|
||||
##### Contact us at {{ email(address="jobs") }} or [jobs@m-labs.ph](mailto:jobs@m-labs.ph)!
|
||||
{% end %}
|
||||
|
|
|
@ -62,7 +62,7 @@ The <a href="https://github.com/quartiq/stabilizer" target="_blank" rel="noopene
|
|||
|
||||
<div class="col-12 col-md-4">
|
||||
<p>
|
||||
To purchase this controller, email <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>. We also offer firmware customizations and development of new features. Note that features that are not implemented in the open source code above (e.g. control from Kasli) are not supported unless purchased separately.
|
||||
To purchase this controller, email {{ email(address="sales") }}. We also offer firmware customizations and development of new features. Note that features that are not implemented in the open source code above (e.g. control from Kasli) are not supported unless purchased separately.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ ARTIQ and the related components that we are developing ([Migen/MiSoC](/gateware
|
|||
|
||||
{% layout_funding(position="center", title="Sponsors") %}
|
||||
|
||||
We acknowledge support from our partners below. Please get in touch ([sales@m-labs.hk](mailto:sales@m-labs.hk)) if you also want to move ARTIQ forward!
|
||||
We acknowledge support from our partners below. Please get in touch ({{ email(address="sales") }}) if you also want to move ARTIQ forward!
|
||||
|
||||
{% end %}
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ template = "page.html"
|
|||
|
||||
**Sinara hardware purchases, ports to your hardware, feature development, technical support, bugfixing**
|
||||
|
||||
contact [sales@m-labs.hk](mailto:sales@m-labs.hk)
|
||||
contact {{ email(address="sales") }}
|
||||
|
||||
We welcome inquiries from research groups of all sizes.<br>[See what has been funded before](/experiment-control/funding)
|
||||
|
||||
|
@ -237,6 +237,6 @@ We welcome inquiries from research groups of all sizes.<br>[See what has been fu
|
|||
</div>
|
||||
|
||||
{% layout_div(css="col-12 text-center") %}
|
||||
Want your project listed here? Write to [sb@m-labs.hk](mailto:sb@m-labs.hk).
|
||||
Want your project listed here? Write to {{ email(address="sb") }}.
|
||||
|
||||
{% end %}
|
||||
|
|
|
@ -14,7 +14,7 @@ The first ARTIQ core devices used hardware built in-house by physicists (based o
|
|||
|
||||
The Sinara hardware is in active development, and the latest information is available <a href="https://github.com/sinara-hw" target="_blank" rel="noopener noreferrer">on the wiki of each project's page</a>. Most of the hardware engineering is done at the <a href="https://www.ise.pw.edu.pl/" target="_blank" rel="noopener noreferrer">Institute for Electronics Systems</a> at the Warsaw University of Technology.
|
||||
|
||||
Kasli and EEMs can be ordered now. We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. Use our [web-based configuration and ordering tool](../place-order), or contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements, and we will establish a quote.
|
||||
Kasli and EEMs can be ordered now. We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. Use our [web-based configuration and ordering tool](../place-order), or contact {{ email(address="sales") }} with your requirements, and we will establish a quote.
|
||||
|
||||
{% end %}
|
||||
|
||||
|
@ -297,7 +297,7 @@ The 4624 AWG "Phaser" is a quad channel 1.25 GS/s RF generator card with dual IQ
|
|||
|
||||
We can deliver a rack-mountable crate that contains all the cards, is fully tested, and is ready to be connected to your experiment and computer network. The lead time can be as short as a few working days and we will provide assistance to help you set up your new equipment with ARTIQ via the online helpdesk. Using our AFWS tool, you can keep the firmware of your M-Labs devices up-to-date easily, and benefit from the new features we continuously develop into ARTIQ.
|
||||
|
||||
Use our [web-based configuration and ordering tool](../place-order), or contact <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a> with your requirements, and we will establish a quote.
|
||||
Use our [web-based configuration and ordering tool](../place-order), or contact <{{ email(address="sales") }} with your requirements, and we will establish a quote.
|
||||
|
||||
{% end %}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ Built on the <a href="/gateware/migen/">MiSoC and Migen</a> technologies that or
|
|||
|
||||
The Mixxeo supported mixing from two DVI or HDMI sources up to 720p60, with crossfade, fade to black and potentially other effects with a latency of less than two frames.
|
||||
|
||||
<b>Status (Aug 2014)</b> - Main board and gateware have been <a href="/images/mixxeo_result.jpg">mostly functional</a> for a while, mechanical design and manufacturing for the case/mechatronics are progressing slowly. If you have the skills and would like to help out, email [sb@m-labs.hk](mailto:sb@m-labs.hk) or the mailing list.
|
||||
<b>Status (Aug 2014)</b> - Main board and gateware have been <a href="/images/mixxeo_result.jpg">mostly functional</a> for a while, mechanical design and manufacturing for the case/mechatronics are progressing slowly. If you have the skills and would like to help out, email {{ email(address="sb") }} or the mailing list.
|
||||
|
||||
<center><img src="/images/mixxeo_menu.png"><br /><br /><img src="/images/mixxeo_board.jpg" class="picimg"></center>
|
||||
|
||||
|
|
|
@ -26,6 +26,6 @@ smoltcp achieves <a href="https://github.com/smoltcp-rs/smoltcp#examplesbenchmar
|
|||
|
||||
The source code is available <a href="https://github.com/smoltcp-rs/smoltcp" rel="noopener noreferrer">on GitHub</a>.
|
||||
|
||||
**Commercial support for smoltcp is available.** Email [sales@m-labs.hk](mailto:sales@m-labs.hk).
|
||||
**Commercial support for smoltcp is available.** Email {{ email(address="sales") }}.
|
||||
|
||||
{% end %}
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,13 @@
|
|||
import React from "react";
|
||||
import {DOMAIN} from "./utils";
|
||||
|
||||
|
||||
export function DomainedEmail({address}) {
|
||||
const target = `${address}@${DOMAIN}`;
|
||||
return <a href={"mailto:" + target}>{target}</a>
|
||||
}
|
||||
|
||||
export const DomainedRFQMessages = {
|
||||
OK: <>We've received your request and will be in contact soon.</>,
|
||||
ERROR: <>We cannot receive your request. Try using the export by coping the configuration and send it to us at <DomainedEmail address="sales"/></>
|
||||
}
|
|
@ -5,7 +5,8 @@ import {SummaryPopup} from "./options/SummaryPopup";
|
|||
|
||||
export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
|
||||
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||
const options = useShopStore((state) => state.crates[crate_index].items[card_index].options);
|
||||
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options);
|
||||
const options = useShopStore((state) => state.crates[crate_index].items[card_index][use_options]);
|
||||
const options_data = useShopStore((state) => state.crates[crate_index].items[card_index].options_data);
|
||||
const card_size = useShopStore((state) => state.crates[crate_index].items[card_index].size);
|
||||
const card_id = useShopStore((state) => state.crates[crate_index].items[card_index].id);
|
||||
|
@ -53,7 +54,8 @@ export function OptionsDialogWrapper({crate_index, card_index, first, last}) {
|
|||
|
||||
export function OptionsSummaryWrapper({crate_index, card_index}) {
|
||||
const card_id = useShopStore((state) => state.crates[crate_index].items[card_index].id);
|
||||
const options = useShopStore((state) => state.crates[crate_index].items[card_index].options);
|
||||
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options);
|
||||
const options = useShopStore((state) => state.crates[crate_index].items[card_index][use_options]);
|
||||
const options_data = useShopStore((state) => state.crates[crate_index].items[card_index].options_data);
|
||||
|
||||
return (
|
||||
|
|
|
@ -25,7 +25,8 @@ export function ProductCartItem({card_index, crate_index, first, last}) {
|
|||
const card_counted_resources = useShopStore(state => state.crates[crate_index].items[card_index].counted_resources, compareObjectsEmptiness);
|
||||
|
||||
const highlighted = useShopStore((state) => state.crates[crate_index].id === state.highlighted.crate && card_index === state.highlighted.card);
|
||||
const options_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
|
||||
const warnings_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
|
||||
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options);
|
||||
const crate_id = useShopStore((state) => state.crates[crate_index].id);
|
||||
const setHighlight = useShopStore((state) => state.highlightCard);
|
||||
const removeHighlight = useShopStore((state) => state.highlightReset);
|
||||
|
@ -35,9 +36,9 @@ export function ProductCartItem({card_index, crate_index, first, last}) {
|
|||
console.log("ProductCartItem renders: ", renderCount)
|
||||
|
||||
|
||||
const options = !options_disabled && card && card.options && card.options.length > 0;
|
||||
const warnings = !options_disabled && card_show_warnings && card_show_warnings.length > 0;
|
||||
const resources = !options_disabled && card_counted_resources && card_counted_resources.length > 0;
|
||||
const options = use_options && card && card[use_options] && card[use_options].length > 0;
|
||||
const warnings = !warnings_disabled && card_show_warnings && card_show_warnings.length > 0;
|
||||
const resources = !warnings_disabled && card_counted_resources && card_counted_resources.length > 0;
|
||||
|
||||
return (
|
||||
<Draggable draggableId={card.id} index={card_index}>
|
||||
|
|
|
@ -6,9 +6,33 @@ import {useShopStore} from "./shop_store";
|
|||
// #!render_count
|
||||
import {useRenderCount} from "@uidotdev/usehooks";
|
||||
|
||||
|
||||
function DatasheetLink({datasheet_file, datasheet_name}) {
|
||||
return datasheet_file && datasheet_name && (<div className="ds">
|
||||
<span className='doc-icon'></span>
|
||||
<a href={datasheet_file} target="_blank" rel="noopener noreferrer">
|
||||
{datasheet_name}
|
||||
</a>
|
||||
</div>)
|
||||
}
|
||||
|
||||
function CardSpecs({specs}) {
|
||||
return specs && specs.length > 0 && (<ul>
|
||||
{specs.map((spec, index) =>
|
||||
<li key={index}>{spec}</li>
|
||||
)}
|
||||
</ul>)
|
||||
}
|
||||
|
||||
function AddButton({onAdd}) {
|
||||
return <button onClick={onAdd}>
|
||||
<img src="/images/shop/icon-add.svg" alt="add"/>
|
||||
</button>
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders a product.
|
||||
* Used in the aside (e.g catalog of product)
|
||||
* Used in the aside (e.g catalog of products)
|
||||
*/
|
||||
export function ProductItem({card_index}) {
|
||||
// #!render_count
|
||||
|
@ -16,33 +40,14 @@ export function ProductItem({card_index}) {
|
|||
|
||||
const getCardDescription = useShopStore((state) => state.getCardDescription);
|
||||
const currency = useShopStore((state) => state.currency);
|
||||
const onAddCard = useShopStore((state) => state.addCardFromCatalog);
|
||||
const addCardFromCatalog = useShopStore((state) => state.addCardFromCatalog);
|
||||
const card = getCardDescription(card_index);
|
||||
|
||||
// #!render_count
|
||||
console.log("ProductItem renders: ", renderCount)
|
||||
|
||||
|
||||
const render_specs = (card.specs && card.specs.length > 0 && (
|
||||
<ul>
|
||||
{card.specs.map((spec, index) =>
|
||||
<li key={index}>{spec}</li>
|
||||
)}
|
||||
</ul>
|
||||
));
|
||||
|
||||
const render_datasheet_link = (card.datasheet_file && card.datasheet_name && (
|
||||
<div className="ds">
|
||||
<span className='doc-icon'></span>
|
||||
<a href={card.datasheet_file} target="_blank" rel="noopener noreferrer">
|
||||
{card.datasheet_name}
|
||||
</a>
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<section className="productItem">
|
||||
|
||||
<div className="content">
|
||||
<h3 style={{'marginBottom': card.name_codename ? '5px' : '20px'}}>{card.name_number} {card.name}</h3>
|
||||
{card.name_codename ? (
|
||||
|
@ -51,16 +56,13 @@ export function ProductItem({card_index}) {
|
|||
|
||||
<div className="price">{`${currency} ${formatMoney(card.price)}`}</div>
|
||||
|
||||
{render_specs}
|
||||
<CardSpecs specs={card.specs}/>
|
||||
|
||||
{render_datasheet_link}
|
||||
<DatasheetLink datasheet_file={card.datasheet_file} datasheet_name={card.datasheet_name}/>
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
|
||||
<button onClick={() => onAddCard(null, card_index, null)}>
|
||||
<img src="/images/shop/icon-add.svg" alt="add"/>
|
||||
</button>
|
||||
<AddButton onAdd={() => addCardFromCatalog(null, card_index, null)} />
|
||||
|
||||
<Draggable draggableId={card.id + card_index} index={card_index}>
|
||||
{(provided, snapshot) => (
|
||||
|
@ -83,10 +85,7 @@ export function ProductItem({card_index}) {
|
|||
</React.Fragment>
|
||||
)}
|
||||
</Draggable>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
);
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ import {Layout} from "./Layout";
|
|||
import {Catalog} from "./Catalog";
|
||||
import {OrderPanel} from "./OrderPanel";
|
||||
import {useShopStore} from "./shop_store";
|
||||
import {DomainedEmail} from "./Domained";
|
||||
|
||||
/**
|
||||
* Component that renders the entire shop
|
||||
|
@ -61,7 +62,7 @@ export function Shop() {
|
|||
the combination would look like. Configure the card settings by tapping on the top of
|
||||
the card; many of the options can be adjusted even after the card has been shipped.
|
||||
If you have any issues with this ordering system, or if you need other configurations,
|
||||
email us directly anytime at <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>.
|
||||
email us directly anytime at <DomainedEmail address="sales"/>.
|
||||
The price excludes shipping, is estimated, and must be confirmed by a quote.
|
||||
</p>
|
||||
)}
|
||||
|
|
|
@ -23,16 +23,16 @@ export function SummaryCrateCard({crate_index, card_index}) {
|
|||
(a, b) => a.id === b.id);
|
||||
const card_show_warnings = useShopStore(state => state.crates[crate_index].items[card_index].show_warnings, compareObjectsEmptiness);
|
||||
const card_options_data = useShopStore(state => state.crates[crate_index].items[card_index].options_data, compareObjectsEmptiness);
|
||||
const options_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
|
||||
|
||||
const warnings_disabled = useShopStore((state) => !!state.crateParams(state.crates[crate_index].crate_mode).warnings_disabled);
|
||||
const use_options = useShopStore((state) => state.crateParams(state.crates[crate_index].crate_mode).options);
|
||||
|
||||
// #!render_count
|
||||
console.log("SummaryCrateCard renders: ", renderCount)
|
||||
|
||||
|
||||
const options = !options_disabled && card && card.options && card.options.length > 0;
|
||||
const options_data = !options_disabled && card_options_data && Object.keys(card_options_data).length > 0;
|
||||
const warnings = !options_disabled && card_show_warnings && card_show_warnings.length > 0;
|
||||
const options = use_options && card && card[use_options] && card[use_options].length > 0;
|
||||
const options_data = card_options_data && Object.keys(card_options_data).length > 0;
|
||||
const warnings = !warnings_disabled && card_show_warnings && card_show_warnings.length > 0;
|
||||
|
||||
return (
|
||||
<tr
|
||||
|
|
|
@ -12,7 +12,7 @@ export function DialogPopup({options, data, target, id, big, first, last, option
|
|||
}
|
||||
);
|
||||
|
||||
let div_classes = `overlayVariant border rounded ${big ? "overlay-bigcard" : "overlay-smallcard"} ${(!big && first) ? "overlay-first" : ""} ${(!big && last) ? "overlay-last" : ""} ${options_class || ""}`;
|
||||
let div_classes = `overlayVariant border rounded ${big ? "overlay-bigcard" : "overlay-smallcard"} ${(!big && first) ? "overlay-first" : ""} ${(!big && last && !first) ? "overlay-last" : ""} ${options_class || ""}`;
|
||||
const handleClick = (_event) => {
|
||||
setShow(!show);
|
||||
};
|
||||
|
|
|
@ -39,9 +39,10 @@ class Line extends Component {
|
|||
<div className="shop-line" key={this.props.id}>
|
||||
<label htmlFor={key} className="form-label">
|
||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||
{this.props.title}:
|
||||
{this.props.title}
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
:
|
||||
</label>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
<input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key} onChange={this.handleChange}
|
||||
value={this.state.text}/>
|
||||
</div>
|
||||
|
|
|
@ -49,8 +49,8 @@ class Switch extends Component {
|
|||
<label className="form-check-label" htmlFor={key} style={{"display": "inline"}}>
|
||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||
{this.props.title}
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
</label>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -65,8 +65,8 @@ class SwitchLine extends Component {
|
|||
<label className="form-check-label" htmlFor={key + "switch"}>
|
||||
{this.props.icon && <img src={`/images${this.props.icon}`} className="options-icon"/>}
|
||||
{this.props.title}
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
</label>
|
||||
{this.props.tip && <Tip id={this.props.id + "tooltip"} tip={this.props.tip}/>}
|
||||
</div>
|
||||
<input type="text" className={`form-control form-control-sm ${this.state.valid ? "" : "options-invalid"}`} id={key + "line"} onChange={this.handleText}
|
||||
value={this.state.text} disabled={!this.state.checked}/>
|
||||
|
|
|
@ -16,6 +16,36 @@ const ipv6 = (params) => {
|
|||
}
|
||||
}
|
||||
|
||||
const hostname = (params) => {
|
||||
const maxHostnameLength = 253;
|
||||
const maxLabelLength = 63;
|
||||
const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
|
||||
|
||||
return (text) => {
|
||||
if (text.length > maxHostnameLength) {
|
||||
return false;
|
||||
}
|
||||
const labels = text.split('.');
|
||||
for (const label of labels) {
|
||||
if (label.length < 1 || label.length > maxLabelLength) {
|
||||
return false;
|
||||
}
|
||||
if (!labelRegex.test(label)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const ipv4OrHost = (params) => {
|
||||
const hostnameLocal = hostname(params);
|
||||
const ipv4Local = ipv4(params);
|
||||
return (text) => {
|
||||
return ipv4Local(text) || hostnameLocal(text);
|
||||
}
|
||||
}
|
||||
|
||||
const ipv4or6 = (params) => {
|
||||
const ipv4Local = ipv4(params);
|
||||
const ipv6Local = ipv6(params);
|
||||
|
@ -47,5 +77,7 @@ export const Validation = {
|
|||
ipv4: ipv4,
|
||||
ipv6: ipv6,
|
||||
ipv4or6: ipv4or6,
|
||||
hostname: hostname,
|
||||
ipv4OrHost: ipv4OrHost,
|
||||
frequency: frequency
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
import {createWithEqualityFn} from "zustand/traditional";
|
||||
import {data as shared_data, itemsUnfoldedList} from "./utils";
|
||||
import {FillExtCrateData, FillExtOrderData, true_type_of} from "./options/utils";
|
||||
import {DATA as shared_data, itemsUnfoldedList, API_RFQ} from "./utils";
|
||||
import {FillExtCrateData, FillExtOrderData} from "./options/utils";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {FillResources} from "./count_resources";
|
||||
import {FillExtCardData} from "./options/utils";
|
||||
|
@ -10,6 +10,7 @@ import {TriggerCrateWarnings, TriggerWarnings} from "./warnings";
|
|||
import {Validation, validateEmail, validateNote, validateJSONInput} from "./validate";
|
||||
import {CratesToJSON, JSONToCrates} from "./json_porter";
|
||||
import {ProcessOptionsToData} from "./options/Options";
|
||||
import {DomainedRFQMessages} from "./Domained";
|
||||
|
||||
|
||||
const cards_to_pn_map = (cards) => {
|
||||
|
@ -18,6 +19,10 @@ const cards_to_pn_map = (cards) => {
|
|||
return result;
|
||||
};
|
||||
|
||||
const toArray = (arg) => {
|
||||
return Array.isArray(arg) ? arg : [arg];
|
||||
};
|
||||
|
||||
const useCatalog = ((set, get) => ({
|
||||
cards: shared_data.items,
|
||||
groups: shared_data.columns.catalog,
|
||||
|
@ -228,7 +233,7 @@ const useSubmitForm = ((set, get) => ({
|
|||
status: Validation.OK,
|
||||
message: ""
|
||||
},
|
||||
API_RFQ: shared_data.API_RFQ,
|
||||
API_RFQ: API_RFQ,
|
||||
email: {
|
||||
value: "",
|
||||
error: null
|
||||
|
@ -306,18 +311,18 @@ const useSubmitForm = ((set, get) => ({
|
|||
if (response.status !== 200) {
|
||||
throw Error("Response status is not OK: " + response.status + ".\n" + response);
|
||||
}
|
||||
get().finishSubmitForm({status: Validation.OK, message: "We've received your request and will be in contact soon."})
|
||||
get().finishSubmitForm({status: Validation.OK, message: DomainedRFQMessages.OK})
|
||||
}, reason => {
|
||||
console.error("Request rejected, reason:", reason)
|
||||
get().finishSubmitForm({
|
||||
status: Validation.Invalid,
|
||||
message: "We cannot receive your request. Try using the export by coping the configuration and send it to us at sales@m-labs.hk"
|
||||
message: DomainedRFQMessages.ERROR
|
||||
})
|
||||
}).catch(err => {
|
||||
console.error("Request failed, reason:", err)
|
||||
get().finishSubmitForm({
|
||||
status: Validation.Invalid,
|
||||
message: "We cannot receive your request. Try using the export by coping the configuration and send it to us at sales@m-labs.hk"
|
||||
message: DomainedRFQMessages.ERROR
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -384,7 +389,7 @@ const useCart = ((set, get) => ({
|
|||
})),
|
||||
setActiveCrate: (id) => set(state => ({active_crate: id})),
|
||||
_addCardFromCatalog: (crate_to, index_from, index_to) => set(state => {
|
||||
const take_from = (true_type_of(index_from) === "array" ? index_from : [index_from]).map((item, _i) => (state.cards_list[item]));
|
||||
const take_from = toArray(index_from).map((item, _i) => (state.cards_list[item]));
|
||||
const dest = crate_to || state.active_crate;
|
||||
if (!dest) return {};
|
||||
return {
|
||||
|
@ -495,10 +500,11 @@ const useCart = ((set, get) => ({
|
|||
fillExtData: (crate_id) => set(state => ({
|
||||
crates: state.crates.map((crate, _i) => {
|
||||
if (crate_id === crate.id) {
|
||||
const options_name = state.crateParams(crate.crate_mode).options;
|
||||
let itemsCopy = Array.from(crate.items);
|
||||
|
||||
itemsCopy = itemsCopy.map((item, index) => {
|
||||
if (!item.options) return item;
|
||||
if (!item[options_name]) return item;
|
||||
if (!item.options_data) item.options_data = {};
|
||||
item.options_data.ext_data = FillExtCardData(itemsCopy, index);
|
||||
return item;
|
||||
|
@ -555,7 +561,8 @@ const useCart = ((set, get) => ({
|
|||
},
|
||||
|
||||
addCardFromCatalog: (crate_to, index_from, index_to, just_mounted) => {
|
||||
const dest = crate_to || get().active_crate;
|
||||
const isCrateless = toArray(index_from).some(value => get().getCardDescription(value).crateless === true);
|
||||
const dest = isCrateless ? "spare" : crate_to || get().active_crate;
|
||||
if (!dest) {
|
||||
console.warn("No destination");
|
||||
get().noDestinationWarning();
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
export const data = window.shop_data;
|
||||
export const itemsUnfoldedList = Array.from(data.columns.catalog.categories.map(groupId => groupId.itemIds).flat());
|
||||
export const DATA = window.shop_data;
|
||||
export const DOMAIN = window.DOMAIN;
|
||||
export const API_RFQ = window.API_RFQ;
|
||||
export const itemsUnfoldedList = Array.from(DATA.columns.catalog.categories.map(groupId => groupId.itemIds).flat());
|
||||
|
||||
export const productStyle = (style, snapshot, removeAnim, hovered, selected, cart=false) => {
|
||||
const custom = {
|
||||
|
@ -91,4 +93,4 @@ export const compareArraysLevelOne = (a, b) =>
|
|||
export function compareObjectsEmptiness(a, b) {
|
||||
return (!a && !b) || (!(!a !== !b) && Object.getPrototypeOf(a) === Object.getPrototypeOf(b) &&
|
||||
(Object.getPrototypeOf(a) !== Object.getPrototypeOf([]) || !!Object.keys(a).length === !!Object.keys(b).length))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
const shop_data = {
|
||||
|
||||
API_RFQ: 'https://hooks.m-labs.hk/rfq',
|
||||
currency: 'USD',
|
||||
|
||||
crateModes: {
|
||||
|
@ -8,20 +6,23 @@ const shop_data = {
|
|||
id: 'rack',
|
||||
name: 'Rack mountable crate',
|
||||
price: 550,
|
||||
hp: 84
|
||||
hp: 84,
|
||||
options: "options"
|
||||
},
|
||||
desktop: {
|
||||
id: 'desktop',
|
||||
name: 'Desktop crate',
|
||||
price: 500,
|
||||
hp: 42
|
||||
hp: 42,
|
||||
options: "options"
|
||||
},
|
||||
no_crate: {
|
||||
id: 'no_crate',
|
||||
name: 'Spare cards',
|
||||
name: 'Spare items',
|
||||
price: 0,
|
||||
hp: -1,
|
||||
warnings_disabled: true
|
||||
warnings_disabled: true,
|
||||
options: "crateless_options"
|
||||
}
|
||||
},
|
||||
crateModeOrder: [
|
||||
|
@ -181,7 +182,6 @@ const shop_data = {
|
|||
'Price includes bitstream generation, flashing, testing, and firmware updates for 1 year (USD 1,400.00).',
|
||||
],
|
||||
size: 'big',
|
||||
type: 'kasli',
|
||||
options: [
|
||||
{type: "Radio", args: {title: "DRTIO role", outvar: "drtio_role", variants: ["standalone", "master", "satellite"], tip: "Distributed Real Time Input/Output allows ARTIQ RTIO channels to be distributed among several satellite devices synchronized and controlled by a central core(master) device. Standalone option disables this feature."}},
|
||||
{
|
||||
|
@ -246,7 +246,6 @@ const shop_data = {
|
|||
'4 MMCX clock outputs.',
|
||||
],
|
||||
size: 'big',
|
||||
type: 'kasli',
|
||||
hp: 8,
|
||||
nbrSlotMin: 0,
|
||||
nbrSlotMax: 12,
|
||||
|
@ -318,7 +317,6 @@ const shop_data = {
|
|||
'A pair of VHDCI carriers is a simple, low-latency and low-cost alternative to DRTIO for some applications.',
|
||||
],
|
||||
size: 'big',
|
||||
type: 'vhdcicarrier',
|
||||
resources: [
|
||||
{name: "eem", max: 8},
|
||||
],
|
||||
|
@ -349,7 +347,6 @@ const shop_data = {
|
|||
datasheet_file: '/docs/sinara-datasheets/2118-2128.pdf',
|
||||
datasheet_name: '2118/2128 BNC/SMA-TTL datasheet',
|
||||
size: 'big',
|
||||
type: null,
|
||||
options: [
|
||||
{
|
||||
"if": [
|
||||
|
@ -459,7 +456,6 @@ const shop_data = {
|
|||
}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source"
|
||||
],
|
||||
|
@ -551,7 +547,6 @@ const shop_data = {
|
|||
}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source"
|
||||
],
|
||||
|
@ -638,7 +633,6 @@ const shop_data = {
|
|||
}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source"
|
||||
],
|
||||
|
@ -701,7 +695,6 @@ const shop_data = {
|
|||
}
|
||||
],
|
||||
size: 'small',
|
||||
type: 'urukul',
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"no_clk_source"
|
||||
|
@ -739,7 +732,6 @@ const shop_data = {
|
|||
fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'small',
|
||||
type: 'urukul',
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"no_clk_source"
|
||||
|
@ -773,7 +765,6 @@ const shop_data = {
|
|||
{type: "Radio", args: {title: "Variant", outvar: "variant", variants: ["Baseband", "Upconverter"], fallback: 1}},
|
||||
],
|
||||
size: 'small',
|
||||
type: 'urukul',
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"no_clk_source"
|
||||
|
@ -816,7 +807,6 @@ const shop_data = {
|
|||
datasheet_file: '/docs/sinara-datasheets/5432.pdf',
|
||||
datasheet_name: '5432 Zotino datasheet',
|
||||
size: 'small',
|
||||
type: 'zotino',
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"idc_resource"
|
||||
|
@ -846,7 +836,6 @@ const shop_data = {
|
|||
'Channels can also be broken out to BNC or SMA using IDC-BNC, IDC-SMA or IDC-MCX cards.'
|
||||
],
|
||||
size: 'small',
|
||||
type: 'zotino',
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"idc_resource",
|
||||
|
@ -872,7 +861,6 @@ const shop_data = {
|
|||
'Breaking out all 32 channels from a Zotino requires 4 IDC-BNC cards.'
|
||||
],
|
||||
size: 'big',
|
||||
type: 'idc-bnc',
|
||||
warnings: [
|
||||
"no_idc_source"
|
||||
],
|
||||
|
@ -931,7 +919,6 @@ const shop_data = {
|
|||
'Breaking out all 32 channels from a Zotino requires 4 SMA-IDC cards.'
|
||||
],
|
||||
size: 'small',
|
||||
type: 'idc-bnc',
|
||||
warnings: [
|
||||
"no_idc_source"
|
||||
],
|
||||
|
@ -951,7 +938,6 @@ const shop_data = {
|
|||
'Connects an external HD68 cable to IDC-BNC, IDC-SMA or IDC-MCX cards.',
|
||||
],
|
||||
size: 'small',
|
||||
type: 'hd68',
|
||||
options: [
|
||||
{type: "Radio", args: {title: "Cable length", outvar: "hd68_cable_len", variants: ["1 M", "2 M", "3 M"], tip: "The desired length of the HD68 cable", fallback: 1}},
|
||||
],
|
||||
|
@ -1008,7 +994,6 @@ const shop_data = {
|
|||
}
|
||||
],
|
||||
size: 'big',
|
||||
type: 'novo',
|
||||
warnings: [
|
||||
"no_eem_source"
|
||||
],
|
||||
|
@ -1035,7 +1020,6 @@ const shop_data = {
|
|||
{type: "Radio", args: {title: "Connectors", outvar: "n_eem", variants: ["1 EEM", "2 EEM", "3 EEM"], tip: "Number of EEM ports to use.", fallback: 1}},
|
||||
],
|
||||
size: 'small',
|
||||
type: 'koster',
|
||||
warnings: [
|
||||
"no_eem_source"
|
||||
],
|
||||
|
@ -1064,7 +1048,6 @@ const shop_data = {
|
|||
],
|
||||
options_class: "clocker",
|
||||
size: 'small',
|
||||
type: 'clocker',
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"no_clk_source",
|
||||
|
@ -1099,13 +1082,26 @@ const shop_data = {
|
|||
validator: {name: "ipv4or6"},
|
||||
fallback: {text: "DHCP", checked: false},
|
||||
tip: "Set up IP address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
|
||||
validator: {name: "ipv4OrHost"},
|
||||
fallback: {text: "mqtt", checked: false},
|
||||
tip: "Set up domain name or IPv4 of the MQTT broker"}},
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
|
||||
{type: "Switch", args: {title: "Term #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
|
||||
{type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
|
||||
],
|
||||
crateless_options: [
|
||||
{type: "SwitchLine", args: {title: "IP", outvar: "ip",
|
||||
validator: {name: "ipv4or6"},
|
||||
fallback: {text: "DHCP", checked: false},
|
||||
tip: "Set up IP address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
|
||||
validator: {name: "ipv4OrHost"},
|
||||
fallback: {text: "mqtt", checked: false},
|
||||
tip: "Set up domain name or IPv4 of the MQTT broker"}},
|
||||
],
|
||||
options_class: "stabilizer",
|
||||
size: 'small',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source"
|
||||
],
|
||||
|
@ -1134,13 +1130,26 @@ const shop_data = {
|
|||
validator: {name: "ipv4or6"},
|
||||
fallback: {text: "DHCP", checked: false},
|
||||
tip: "Set up IP address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
|
||||
validator: {name: "ipv4OrHost"},
|
||||
fallback: {text: "mqtt", checked: false},
|
||||
tip: "Set up domain name or IPv4 of the MQTT broker"}},
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
|
||||
{type: "Switch", args: {title: "Term #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
|
||||
{type: "Switch", args: {title: "Term #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
|
||||
],
|
||||
crateless_options: [
|
||||
{type: "SwitchLine", args: {title: "IP", outvar: "ip",
|
||||
validator: {name: "ipv4or6"},
|
||||
fallback: {text: "DHCP", checked: false},
|
||||
tip: "Set up IP address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
|
||||
validator: {name: "ipv4OrHost"},
|
||||
fallback: {text: "mqtt", checked: false},
|
||||
tip: "Set up domain name or IPv4 of the MQTT broker"}},
|
||||
],
|
||||
options_class: "stabilizer",
|
||||
size: 'small',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source"
|
||||
],
|
||||
|
@ -1170,7 +1179,6 @@ const shop_data = {
|
|||
fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"no_clk_source"
|
||||
|
@ -1199,7 +1207,6 @@ const shop_data = {
|
|||
fallback: {text: "125 MHz", checked: false}}}
|
||||
],
|
||||
size: 'big',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"no_clk_source"
|
||||
|
@ -1226,10 +1233,27 @@ const shop_data = {
|
|||
'100Base-T Ethernet with PoE.'
|
||||
],
|
||||
options: [
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}}
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
|
||||
{type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip",
|
||||
validator: {name: "ipv4"},
|
||||
fallback: {text: "0.0.0.0", checked: false},
|
||||
tip: "Set up static IPv4 address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
|
||||
validator: {name: "ipv4OrHost"},
|
||||
fallback: {text: "mqtt", checked: false},
|
||||
tip: "Set up domain name or IPv4 of the MQTT broker"}},
|
||||
],
|
||||
crateless_options: [
|
||||
{type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip",
|
||||
validator: {name: "ipv4"},
|
||||
fallback: {text: "0.0.0.0", checked: false},
|
||||
tip: "Set up static IPv4 address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
|
||||
validator: {name: "ipv4OrHost"},
|
||||
fallback: {text: "mqtt", checked: false},
|
||||
tip: "Set up domain name or IPv4 of the MQTT broker"}},
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source"
|
||||
],
|
||||
|
@ -1252,8 +1276,13 @@ const shop_data = {
|
|||
'100Base-T Ethernet with PoE.',
|
||||
'Can stabilize temperature of Sinara 5432 DAC or external devices containing TEC and thermistor.'
|
||||
],
|
||||
crateless_options: [
|
||||
{type: "SwitchLine", args: {title: "IPv4", outvar: "ip",
|
||||
validator: {name: "ipv4"},
|
||||
fallback: {text: "192.168.1.26/24", checked: false},
|
||||
tip: "Set up IP address used by the device"}},
|
||||
],
|
||||
size: 'small',
|
||||
type: null,
|
||||
consumes: {
|
||||
hp: 4
|
||||
},
|
||||
|
@ -1277,7 +1306,6 @@ const shop_data = {
|
|||
'Included remote analog front-end (AFE) board converts differential signals to ±10V single-ended at the point of use, with additional gain and filtering.',
|
||||
],
|
||||
size: 'big',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"no_clk_source"
|
||||
|
@ -1305,14 +1333,27 @@ const shop_data = {
|
|||
validator: {name: "ipv4or6"},
|
||||
fallback: {text: "DHCP", checked: false},
|
||||
tip: "Set up IP address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
|
||||
validator: {name: "ipv4OrHost"},
|
||||
fallback: {text: "mqtt", checked: false},
|
||||
tip: "Set up domain name or IPv4 of the MQTT broker"}},
|
||||
{type: "Switch", args: {title: "Ext power", outvar: "ext_pwr", "tip": "Use external power supply in order to reduce number of used EEM connectors"}},
|
||||
{type: "SwitchLine", args: {title: "Ext CLK", outvar: "ext_clk",
|
||||
fallback: {text: "125 MHz", checked: false}, validator: {name: "frequency", params: {min: 10e6, max: 1e9}}}},
|
||||
{type: "Switch", args: {title: "Termination #0", outvar: "term_0", tip: "Enable termination on ADC channel #0"}},
|
||||
{type: "Switch", args: {title: "Termination #1", outvar: "term_1", tip: "Enable termination on ADC channel #1"}}
|
||||
],
|
||||
crateless_options: [
|
||||
{type: "SwitchLine", args: {title: "Static IPv4", outvar: "ip",
|
||||
validator: {name: "ipv4"},
|
||||
fallback: {text: "0.0.0.0", checked: false},
|
||||
tip: "Set up static IPv4 address used by the device"}},
|
||||
{type: "SwitchLine", args: {title: "MQTT broker address", outvar: "broker",
|
||||
validator: {name: "ipv4OrHost"},
|
||||
fallback: {text: "mqtt", checked: false},
|
||||
tip: "Set up domain name or IPv4 of the MQTT broker"}},
|
||||
],
|
||||
size: 'big',
|
||||
type: null,
|
||||
warnings: [
|
||||
"no_eem_source",
|
||||
"no_clk_source"
|
||||
|
@ -1342,12 +1383,31 @@ const shop_data = {
|
|||
"Optional - external power brick will be shipped free of charge if removed."
|
||||
],
|
||||
size: 'big',
|
||||
type: null,
|
||||
warnings: [],
|
||||
consumes: {
|
||||
hp: 4,
|
||||
},
|
||||
},
|
||||
'afws': {
|
||||
id: 'afws',
|
||||
name: 'Subscription',
|
||||
name_number: 'AFWS',
|
||||
name_codename: '',
|
||||
price: 800,
|
||||
image: '/images/shop/graphic-03_AFWS.svg',
|
||||
specs: [
|
||||
"Artiq Firmware Service for one variant for one year.",
|
||||
"Includes support at helpdesk.",
|
||||
"Included with purchase of any Carrier with no additional cost.",
|
||||
],
|
||||
crateless: true,
|
||||
crateless_options: [
|
||||
{type: "Line", args: {title: "Variant name", outvar: "variant_name", fallback: "",
|
||||
tip: "Variant name can be found on the sticker on top of the crate. If you don't have one, leave the preferred name here."}},
|
||||
],
|
||||
size: 'big',
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
|
||||
columns: {
|
||||
|
@ -1403,6 +1463,7 @@ const shop_data = {
|
|||
itemIds: [
|
||||
'koster',
|
||||
'eem_pwr_mod',
|
||||
'afws',
|
||||
]}
|
||||
],
|
||||
},
|
||||
|
@ -1417,7 +1478,7 @@ const shop_data = {
|
|||
},
|
||||
{
|
||||
id: "spare",
|
||||
name: "Spare cards",
|
||||
name: "Spare items",
|
||||
crate_mode: "no_crate",
|
||||
items: [],
|
||||
warnings: [],
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
<link href="{{ get_url(path='favicon.ico', cachebust=true) }}" rel="icon" type="image/x-icon">
|
||||
|
||||
{% block styles %}{% endblock %}
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<h5 class="pb-3">Sales inquiries</h5>
|
||||
|
||||
<p>
|
||||
To purchase ARTIQ Sinara hardware, software and gateware development services, or to enter a technical support agreement, write to <a href="mailto:sales@m-labs.hk">sales@m-labs.hk</a>.
|
||||
To purchase ARTIQ Sinara hardware, software and gateware development services, or to enter a technical support agreement, write to <a href="mailto:sales@{{get_env(name="DOMAINNAME", default="m-labs.hk")}}">sales@{{get_env(name="DOMAINNAME", default="m-labs.hk")}}</a>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@
|
|||
<h5 class="pb-3">Commercial technical support</h5>
|
||||
|
||||
<p>
|
||||
If you need help setting up ARTIQ hardware purchased from us, or if you have a commercial technical support agreement with us, email us anytime at <a href="mailto:helpdesk@m-labs.hk">helpdesk@m-labs.hk</a>.
|
||||
If you need help setting up ARTIQ hardware purchased from us, or if you have a commercial technical support agreement with us, email us anytime at <a href="mailto:helpdesk@{{get_env(name="DOMAINNAME", default="m-labs.hk")}}">helpdesk@{{get_env(name="DOMAINNAME", default="m-labs.hk")}}</a>.
|
||||
</p>
|
||||
|
||||
<h5 class="pb-3">Community support</h5>
|
||||
|
|
|
@ -46,6 +46,11 @@
|
|||
<!-- Load Data -->
|
||||
<script src="{{ get_url(path='js/shop_data.js', cachebust=true) }}"></script>
|
||||
|
||||
<script>
|
||||
window.DOMAIN = "{{get_env(name="DOMAINNAME", default="m-labs.hk")}}";
|
||||
window.API_RFQ = `https://hooks.${window.DOMAIN}/rfq`;
|
||||
</script>
|
||||
|
||||
<!-- Load our React component. -->
|
||||
<!-- <script type="text/babel" src="{{ get_url(path='js/shop.jsx', cachebust=true) }}"></script> -->
|
||||
<script src="{{ get_url(path='js/shop.bundle.js', cachebust=true) }}"></script>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<a href="mailto:{{address}}@{{get_env(name="DOMAINNAME", default="m-labs.hk")}}">{{address}}@{{get_env(name="DOMAINNAME", default="m-labs.hk")}}</a>
|
Loading…
Reference in New Issue