Compare commits

...

7 Commits

Author SHA1 Message Date
d36138c800 Update App.scss 2023-12-03 22:46:49 -05:00
1ed6a622d4 🌜 Trying dark mode on the react-select 2023-12-03 16:02:54 -05:00
29e0772a10 🌜 Initial Dark Mode support 2023-12-03 15:28:05 -05:00
57b713aca1 🏗️ Refactored the AirDC++ download panel 2023-12-02 11:38:17 -05:00
dfd99e45b6 🔧 Implementing download method 2023-11-29 23:24:34 -05:00
591ecb394c 🔧 Formatted the search query box 2023-11-29 23:08:00 -05:00
145427d3fd 🏗️ Acquisition Panel refactor WIP 2023-11-29 21:22:48 -05:00
12 changed files with 216 additions and 103 deletions

View File

@@ -1,10 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="dark">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Three Two!</title> <title>Three Two!</title>
</head> </head>
@@ -12,5 +11,4 @@
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/client/index.tsx"></script> <script type="module" src="/src/client/index.tsx"></script>
</body> </body>
</html> </html>

View File

@@ -31,6 +31,7 @@
"axios-cache-interceptor": "^1.0.1", "axios-cache-interceptor": "^1.0.1",
"axios-rate-limit": "^1.3.0", "axios-rate-limit": "^1.3.0",
"babel-plugin-styled-components": "^2.1.4", "babel-plugin-styled-components": "^2.1.4",
"bulma-prefers-dark": "^0.1.0-beta.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"dayjs": "^1.10.6", "dayjs": "^1.10.6",
"ellipsize": "^0.5.1", "ellipsize": "^0.5.1",

View File

@@ -1,4 +1,5 @@
@import "/node_modules/bulma/bulma.sass"; @import "/node_modules/bulma/bulma.sass";
@import "/node_modules/bulma-prefers-dark/bulma-prefers-dark.sass";
$fa-font-path: "/node_modules/@fortawesome/fontawesome-free/webfonts"; $fa-font-path: "/node_modules/@fortawesome/fontawesome-free/webfonts";
@import "/node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss"; @import "/node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
@import "/node_modules/@fortawesome/fontawesome-free/scss/regular.scss"; @import "/node_modules/@fortawesome/fontawesome-free/scss/regular.scss";
@@ -14,8 +15,9 @@ $flexSize: 4em;
$boxSpacing: 1em; $boxSpacing: 1em;
$colorText: #404646; $colorText: #404646;
body { body {
background: #fffcc; background: #20292f;
} }
.is-size-8 { .is-size-8 {
font-size: $size-8; font-size: $size-8;
} }
@@ -72,10 +74,6 @@ pre {
background: #454a59; background: #454a59;
} }
body {
background: #454a59;
}
.pulsating-circle { .pulsating-circle {
position: relative; position: relative;
left: -120%; left: -120%;
@@ -244,12 +242,16 @@ pre {
.generic-card { .generic-card {
display: inline-block; display: inline-block;
background-color: #fff; background-color: hsl(232, 11%, 15%);
border-top-left-radius: 0.4rem; border-top-left-radius: 0.4rem;
border-top-right-radius: 0.4rem; border-top-right-radius: 0.4rem;
border-bottom-left-radius: 0.4rem; border-bottom-left-radius: 0.4rem;
border-bottom-right-radius: 0.4rem; border-bottom-right-radius: 0.4rem;
box-shadow: 1px 8px 23px 7px rgba(0, 0, 0, 0.12); box-shadow: inset 0 0 0.5px 1px hsla(0, 0%, 100%, 0.1),
/* 2. shadow ring 👇 */ 0 0 0 1px hsla(230, 13%, 9%, 0.075),
/* 3. multiple soft shadows 👇 */ 0 0.3px 0.4px hsla(230, 13%, 9%, 0.02),
0 0.9px 1.5px hsla(230, 13%, 9%, 0.045),
0 3.5px 6px hsla(230, 13%, 9%, 0.09);
.green-border { .green-border {
border: 1px dotted #168b64; border: 1px dotted #168b64;
@@ -358,7 +360,7 @@ pre {
// raw file details // raw file details
.raw-file-details { .raw-file-details {
padding: 1rem; padding: 1rem;
background-color: beige; background: #30475e;
border-radius: 0.5rem; border-radius: 0.5rem;
} }
@@ -368,23 +370,23 @@ pre {
// comicvine metadata // comicvine metadata
.comicvine-metadata { .comicvine-metadata {
background-color: #f2f1f9; background-color: #d6cc99;
padding: 0.8rem; padding: 0.8rem;
border-radius: 0.5rem; border-radius: 0.5rem;
} }
.issue-metadata { .issue-metadata {
background-color: #fbffee; background-color: #57615c;
padding: 0.8em; padding: 0.8em;
border-radius: 0.5rem; border-radius: 0.5rem;
.name { .name {
font-size: 0.95rem; font-size: 0.95rem;
color: #4a4f50;
} }
} }
.comicInfo-metadata { .comicInfo-metadata {
background-color: #f7ebdd; background-color: #d6cc99;
color: #000;
padding: 0.8rem; padding: 0.8rem;
border-radius: 0.5rem; border-radius: 0.5rem;
} }
@@ -421,17 +423,17 @@ pre {
} }
// Library // Library
.header-area { .header-area {
background: #20292f;
width: 100%; width: 100%;
padding: 25px 0 15px 0; padding: 25px 0 15px 0;
position: sticky; position: sticky;
z-index: 9999; z-index: 9;
background: #fffffc; top: 57px;
top: 50px;
} }
.library { .library {
.table-controls { .table-controls {
background: #fffffc; background: #20292f;
justify-content: space-between; justify-content: space-between;
position: sticky; position: sticky;
top: 126px; top: 126px;
@@ -439,17 +441,17 @@ pre {
} }
.pagination { .pagination {
margin: 0; margin: 0;
background: #fffffc;
} }
table { table {
background: #20292f;
border-collapse: separate; border-collapse: separate;
width: 100%; width: 100%;
thead { thead {
background: #20292f;
position: sticky; position: sticky;
top: 250px; top: 250px;
z-index: 1; z-index: 1;
background: #fffffc;
min-height: 130px; min-height: 130px;
} }
tr { tr {

View File

@@ -1,9 +1,5 @@
import React, { useCallback, ReactElement, useEffect, useState } from "react"; import React, { useCallback, ReactElement, useEffect, useState } from "react";
import { import { getBundlesForComic, sleep } from "../../actions/airdcpp.actions";
downloadAirDCPPItem,
getBundlesForComic,
sleep,
} from "../../actions/airdcpp.actions";
import { SearchQuery, PriorityEnum, SearchResponse } from "threetwo-ui-typings"; import { SearchQuery, PriorityEnum, SearchResponse } from "threetwo-ui-typings";
import { RootState, SearchInstance } from "threetwo-ui-typings"; import { RootState, SearchInstance } from "threetwo-ui-typings";
import ellipsize from "ellipsize"; import ellipsize from "ellipsize";
@@ -13,6 +9,7 @@ import { isEmpty, isNil, map } from "lodash";
import { useStore } from "../../store"; import { useStore } from "../../store";
import { useShallow } from "zustand/react/shallow"; import { useShallow } from "zustand/react/shallow";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import axios from "axios";
interface IAcquisitionPanelProps { interface IAcquisitionPanelProps {
query: any; query: any;
@@ -28,14 +25,22 @@ export const AcquisitionPanel = (
airDCPPSocketInstance, airDCPPSocketInstance,
airDCPPClientConfiguration, airDCPPClientConfiguration,
airDCPPSessionInformation, airDCPPSessionInformation,
airDCPPDownloadTick,
} = useStore( } = useStore(
useShallow((state) => ({ useShallow((state) => ({
airDCPPSocketInstance: state.airDCPPSocketInstance, airDCPPSocketInstance: state.airDCPPSocketInstance,
airDCPPClientConfiguration: state.airDCPPClientConfiguration, airDCPPClientConfiguration: state.airDCPPClientConfiguration,
airDCPPSessionInformation: state.airDCPPSessionInformation, airDCPPSessionInformation: state.airDCPPSessionInformation,
airDCPPDownloadTick: state.airDCPPDownloadTick,
})), })),
); );
interface SearchData {
query: Pick<SearchQuery, "pattern"> & Partial<Omit<SearchQuery, "pattern">>;
hub_urls: string[] | undefined | null;
priority: PriorityEnum;
}
/** /**
* Get the hubs list from an AirDCPP Socket * Get the hubs list from an AirDCPP Socket
*/ */
@@ -43,34 +48,15 @@ export const AcquisitionPanel = (
queryKey: ["hubs"], queryKey: ["hubs"],
queryFn: async () => await airDCPPSocketInstance.get(`hubs`), queryFn: async () => await airDCPPSocketInstance.get(`hubs`),
}); });
const { comicObjectId } = props;
const issueName = props.query.issue.name || ""; const issueName = props.query.issue.name || "";
// const { settings } = props;
const sanitizedIssueName = issueName.replace(/[^a-zA-Z0-9 ]/g, " "); const sanitizedIssueName = issueName.replace(/[^a-zA-Z0-9 ]/g, " ");
// Selectors for picking state
// const airDCPPSearchResults = useSelector((state: RootState) => {
// return state.airdcpp.searchResults;
// });
// const isAirDCPPSearchInProgress = useSelector(
// (state: RootState) => state.airdcpp.isAirDCPPSearchInProgress,
// );
// const searchInfo = useSelector(
// (state: RootState) => state.airdcpp.searchInfo,
// );
// const searchInstance: SearchInstance = useSelector(
// (state: RootState) => state.airdcpp.searchInstance,
// );
// const settings = useSelector((state: RootState) => state.settings.data);
// const airDCPPConfiguration = useContext(AirDCPPSocketContext);
interface SearchData {
query: Pick<SearchQuery, "pattern"> & Partial<Omit<SearchQuery, "pattern">>;
hub_urls: string[] | undefined | null;
priority: PriorityEnum;
}
const [dcppQuery, setDcppQuery] = useState({}); const [dcppQuery, setDcppQuery] = useState({});
const [airDCPPSearchResults, setAirDCPPSearchResults] = useState([]); const [airDCPPSearchResults, setAirDCPPSearchResults] = useState([]);
const [airDCPPSearchStatus, setAirDCPPSearchStatus] = useState(false);
const [airDCPPSearchInstance, setAirDCPPSearchInstance] = useState({});
const [airDCPPSearchInfo, setAirDCPPSearchInfo] = useState({});
// Construct a AirDC++ query based on metadata inferred, upon component mount // Construct a AirDC++ query based on metadata inferred, upon component mount
// Pre-populate the search input with the search string, so that // Pre-populate the search input with the search string, so that
@@ -88,15 +74,18 @@ export const AcquisitionPanel = (
setDcppQuery(dcppSearchQuery); setDcppQuery(dcppSearchQuery);
}, []); }, []);
/**
* Method to perform a search via an AirDC++ websocket
* @param {SearchData} data - a SearchData query
* @param {any} ADCPPSocket - an intialized AirDC++ socket instance
*/
const search = async (data: SearchData, ADCPPSocket: any) => { const search = async (data: SearchData, ADCPPSocket: any) => {
try { try {
if (!ADCPPSocket.isConnected()) { if (!ADCPPSocket.isConnected()) {
await ADCPPSocket(); await ADCPPSocket();
} }
const instance: SearchInstance = await ADCPPSocket.post("search"); const instance: SearchInstance = await ADCPPSocket.post("search");
// dispatch({ setAirDCPPSearchStatus(true);
// type: AIRDCPP_SEARCH_IN_PROGRESS,
// });
// We want to get notified about every new result in order to make the user experience better // We want to get notified about every new result in order to make the user experience better
await ADCPPSocket.addListener( await ADCPPSocket.addListener(
@@ -142,6 +131,9 @@ export const AcquisitionPanel = (
const currentInstance = await ADCPPSocket.get( const currentInstance = await ADCPPSocket.get(
`search/${instance.id}`, `search/${instance.id}`,
); );
setAirDCPPSearchInstance(currentInstance);
setAirDCPPSearchInfo(searchInfo);
console.log("Asdas", airDCPPSearchInfo);
if (currentInstance.result_count === 0) { if (currentInstance.result_count === 0) {
// ...nothing was received, show an informative message to the user // ...nothing was received, show an informative message to the user
console.log("No more search results."); console.log("No more search results.");
@@ -149,11 +141,8 @@ export const AcquisitionPanel = (
// The search can now be considered to be "complete" // The search can now be considered to be "complete"
// If there's an "in progress" indicator in the UI, that could also be disabled here // If there's an "in progress" indicator in the UI, that could also be disabled here
// dispatch({ setAirDCPPSearchInstance(instance);
// type: AIRDCPP_HUB_SEARCHES_SENT, setAirDCPPSearchStatus(false);
// searchInfo,
// instance,
// });
}, },
instance.id, instance.id,
); );
@@ -164,6 +153,68 @@ export const AcquisitionPanel = (
throw error; throw error;
} }
}; };
/**
* Method to download a bundle associated with a search result from AirDC++
* @param {Number} searchInstanceId - description
* @param {String} resultId - description
* @param {String} comicObjectId - description
* @param {String} name - description
* @param {Number} size - description
* @param {any} type - description
* @param {any} ADCPPSocket - description
* @returns {void} - description
*/
const download = async (
searchInstanceId: Number,
resultId: String,
comicObjectId: String,
name: String,
size: Number,
type: any,
ADCPPSocket: any,
): void => {
try {
if (!ADCPPSocket.isConnected()) {
await ADCPPSocket.connect();
}
let bundleDBImportResult = {};
const downloadResult = await ADCPPSocket.post(
`search/${searchInstanceId}/results/${resultId}/download`,
);
if (!isNil(downloadResult)) {
bundleDBImportResult = await axios({
method: "POST",
url: `http://localhost:3000/api/library/applyAirDCPPDownloadMetadata`,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
data: {
bundleId: downloadResult.bundle_info.id,
comicObjectId,
name,
size,
type,
},
});
// dispatch({
// type: AIRDCPP_RESULT_DOWNLOAD_INITIATED,
// downloadResult,
// bundleDBImportResult,
// });
//
// dispatch({
// type: IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
// comicBookDetail: bundleDBImportResult.data,
// IMS_inProgress: false,
// });
}
} catch (error) {
throw error;
}
};
const getDCPPSearchResults = async (searchQuery) => { const getDCPPSearchResults = async (searchQuery) => {
const manualQuery = { const manualQuery = {
query: { query: {
@@ -209,7 +260,6 @@ export const AcquisitionPanel = (
}, },
[], [],
); );
console.log("yaman", airDCPPSearchResults);
return ( return (
<> <>
<div className="comic-detail columns"> <div className="comic-detail columns">
@@ -245,7 +295,7 @@ export const AcquisitionPanel = (
<button <button
type="submit" type="submit"
className={ className={
false airDCPPSearchStatus
? "button is-loading is-warning" ? "button is-loading is-warning"
: "button is-success is-light" : "button is-success is-light"
} }
@@ -273,6 +323,60 @@ export const AcquisitionPanel = (
)} )}
</div> </div>
{/* AirDC++ search instance details */}
{!isNil(airDCPPSearchInstance) &&
!isEmpty(airDCPPSearchInfo) &&
!isNil(hubs) && (
<div className="columns">
<div className="column is-one-quarter is-size-7">
<div className="card">
<div className="card-content">
<dl>
<dt>
<div className="tags mb-1">
{hubs.map((value, idx) => (
<span className="tag is-warning" key={idx}>
{value.identity.name}
</span>
))}
</div>
</dt>
<dt>
Query:
<span className="has-text-weight-semibold">
{airDCPPSearchInfo.query.pattern}
</span>
</dt>
<dd>
Extensions:
<span className="has-text-weight-semibold">
{airDCPPSearchInfo.query.extensions.join(", ")}
</span>
</dd>
<dd>
File type:
<span className="has-text-weight-semibold">
{airDCPPSearchInfo.query.file_type}
</span>
</dd>
</dl>
</div>
</div>
</div>
<div className="column is-one-quarter is-size-7">
<div className="card">
<div className="card-content">
<dl>
<dt>Search Instance: {airDCPPSearchInstance.id}</dt>
<dt>Owned by {airDCPPSearchInstance.owner}</dt>
<dd>Expires in: {airDCPPSearchInstance.expires_in}</dd>
</dl>
</div>
</div>
</div>
</div>
)}
{/* AirDC++ results */} {/* AirDC++ results */}
<div className="columns"> <div className="columns">
{!isNil(airDCPPSearchResults) && !isEmpty(airDCPPSearchResults) ? ( {!isNil(airDCPPSearchResults) && !isEmpty(airDCPPSearchResults) ? (
@@ -342,12 +446,14 @@ export const AcquisitionPanel = (
<button <button
className="button is-small is-light is-success" className="button is-small is-light is-success"
onClick={() => onClick={() =>
downloadDCPPResult( download(
searchInstance.id, airDCPPSearchInstance.id,
result.id, result.id,
comicObjectId,
result.name, result.name,
result.size, result.size,
result.type, result.type,
airDCPPSocketInstance,
) )
} }
> >

View File

@@ -69,17 +69,29 @@ export const Menu = (props): ReactElement => {
break; break;
} }
}; };
const customStyles = {
option: (base, { data, isDisabled, isFocused, isSelected }) => {
return {
...base,
backgroundColor: isFocused ? "gray" : "black",
};
},
control: (base) => ({
...base,
backgroundColor: "black",
border: "1px solid #CCC",
}),
};
return ( return (
<Select <Select
className="basic-single"
classNamePrefix="select"
components={{ Placeholder }} components={{ Placeholder }}
placeholder={ placeholder={
<span> <span>
<i className="fa-solid fa-list"></i> Actions <i className="fa-solid fa-list"></i> Actions
</span> </span>
} }
styles={customStyles}
name="actions" name="actions"
isSearchable={false} isSearchable={false}
options={filteredActionOptions} options={filteredActionOptions}

View File

@@ -4,9 +4,9 @@ import React, { ReactElement } from "react";
export const DownloadProgressTick = (props): ReactElement => { export const DownloadProgressTick = (props): ReactElement => {
return ( return (
<div> <div>
<h4 className="is-size-6">{props.data.name}</h4> <h4 className="is-size-5">{props.data.name}</h4>
<div> <div>
<span className="is-size-3 has-text-weight-semibold"> <span className="is-size-4 has-text-weight-semibold">
{prettyBytes(props.data.downloaded_bytes)} of{" "} {prettyBytes(props.data.downloaded_bytes)} of{" "}
{prettyBytes(props.data.size)}{" "} {prettyBytes(props.data.size)}{" "}
</span> </span>
@@ -20,13 +20,12 @@ export const DownloadProgressTick = (props): ReactElement => {
% %
</progress> </progress>
</div> </div>
<div className="is-size-5"> <div className="is-size-6 mt-1 mb-2">
{prettyBytes(props.data.speed)} per second. <p>{prettyBytes(props.data.speed)} per second.</p>
</div>
<div className="is-size-5">
Time left: Time left:
{Math.round(parseInt(props.data.seconds_left) / 60)} {Math.round(parseInt(props.data.seconds_left) / 60)}
</div> </div>
<div>{props.data.target}</div> <div>{props.data.target}</div>
</div> </div>
); );

View File

@@ -7,7 +7,7 @@ export const RawFileDetails = (props): ReactElement => {
const { rawFileDetails, inferredMetadata } = props.data; const { rawFileDetails, inferredMetadata } = props.data;
return ( return (
<> <>
<div className="comic-detail raw-file-details column is-three-fifths"> <div className="comic-detail raw-file-details column is-three-quarters">
<dl> <dl>
<dt>Raw File Details</dt> <dt>Raw File Details</dt>
<dd className="is-size-7"> <dd className="is-size-7">

View File

@@ -1,13 +1,5 @@
import React, { import React, { ReactElement, useEffect, useState } from "react";
ReactElement,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { getTransfers } from "../../actions/airdcpp.actions"; import { getTransfers } from "../../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux";
import { AirDCPPSocketContext } from "../../context/AirDCPPSocket";
import { isEmpty, isNil, isUndefined } from "lodash"; import { isEmpty, isNil, isUndefined } from "lodash";
import { determineCoverFile } from "../../shared/utils/metadata.utils"; import { determineCoverFile } from "../../shared/utils/metadata.utils";
import MetadataPanel from "../shared/MetadataPanel"; import MetadataPanel from "../shared/MetadataPanel";
@@ -17,18 +9,18 @@ interface IDownloadsProps {
} }
export const Downloads = (props: IDownloadsProps): ReactElement => { export const Downloads = (props: IDownloadsProps): ReactElement => {
const airDCPPConfiguration = useContext(AirDCPPSocketContext); // const airDCPPConfiguration = useContext(AirDCPPSocketContext);
const { const {
airDCPPState: { settings, socket }, airDCPPState: { settings, socket },
} = airDCPPConfiguration; } = airDCPPConfiguration;
const dispatch = useDispatch(); // const dispatch = useDispatch();
const airDCPPTransfers = useSelector( // const airDCPPTransfers = useSelector(
(state: RootState) => state.airdcpp.transfers, // (state: RootState) => state.airdcpp.transfers,
); // );
const issueBundles = useSelector( // const issueBundles = useSelector(
(state: RootState) => state.airdcpp.issue_bundles, // (state: RootState) => state.airdcpp.issue_bundles,
); // );
const [bundles, setBundles] = useState([]); const [bundles, setBundles] = useState([]);
// Make the call to get all transfers from AirDC++ // Make the call to get all transfers from AirDC++
useEffect(() => { useEffect(() => {

View File

@@ -226,7 +226,7 @@ export const Import = (props: IProps): ReactElement => {
{!isLoading && !isEmpty(data?.data) && ( {!isLoading && !isEmpty(data?.data) && (
<> <>
<h3 className="subtitle is-4 mt-5">Past Imports</h3> <h3 className="subtitle is-4 mt-5">Past Imports</h3>
<table className="table"> <table className="table is-striped">
<thead> <thead>
<tr> <tr>
<th>Time Started</th> <th>Time Started</th>

View File

@@ -35,19 +35,16 @@ export const AirDCPPHubsForm = (): ReactElement => {
* Get the hubs list from an AirDCPP Socket * Get the hubs list from an AirDCPP Socket
*/ */
const { data: hubs } = useQuery({ const { data: hubs } = useQuery({
queryKey: [], queryKey: ["hubs"],
queryFn: async () => await airDCPPSocketInstance.get(`hubs`), queryFn: async () => await airDCPPSocketInstance.get(`hubs`),
enabled: !!settings,
}); });
let hubList = {}; let hubList = {};
if (!isEmpty(hubs)) { if (!isNil(hubs)) {
console.log("hs", hubs);
hubList = hubs.map(({ hub_url, identity }) => ({ hubList = hubs.map(({ hub_url, identity }) => ({
value: hub_url, value: hub_url,
label: identity.name, label: identity.name,
})); }));
} }
console.log(hubList);
const { mutate } = useMutation({ const { mutate } = useMutation({
mutationFn: async (values) => mutationFn: async (values) =>
await axios({ await axios({

View File

@@ -23,6 +23,7 @@ export const useStore = create((set, get) => ({
airDCPPSocketConnected: value, airDCPPSocketConnected: value,
})), })),
airDCPPDownloadTick: {}, airDCPPDownloadTick: {},
airDCPPTransfers: {},
// Socket.io state // Socket.io state
socketIOInstance: {}, socketIOInstance: {},

View File

@@ -4310,6 +4310,11 @@ builtin-modules@^3.3.0:
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
bulma-prefers-dark@^0.1.0-beta.1:
version "0.1.0-beta.1"
resolved "https://registry.yarnpkg.com/bulma-prefers-dark/-/bulma-prefers-dark-0.1.0-beta.1.tgz#074aa71899f389a0137dd3753f0d89e96ab1e59b"
integrity sha512-ti4sKxIIrTAvGtsYc9Rk66SUZSH/j63EU1hApQijQVlKFF0qBLGSb8E16HhI83KJaIeYP4aAHQv2tj0ara831A==
bulma@^0.9.4: bulma@^0.9.4:
version "0.9.4" version "0.9.4"
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.4.tgz#0ca8aeb1847a34264768dba26a064c8be72674a1" resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.4.tgz#0ca8aeb1847a34264768dba26a064c8be72674a1"