256 lines
6.7 KiB
JavaScript
256 lines
6.7 KiB
JavaScript
import { sendAPIQuery } from '../../download/api/index.mjs';
|
|
import { apiCacheKey, getAPICache, clearAPICache } from '../../download/api/cache.mjs';
|
|
import { runConcurrentQueries } from '../../download/api/queue.mjs';
|
|
import '../../download/api/config.mjs';
|
|
import '../../download/api/fetch.mjs';
|
|
import 'fs';
|
|
import 'crypto';
|
|
import '../../misc/scan.mjs';
|
|
|
|
function identicalDates(actual, expected) {
|
|
if (typeof actual !== "string") {
|
|
return false;
|
|
}
|
|
if (actual === expected) {
|
|
return true;
|
|
}
|
|
return new Date(actual).toString() === new Date(expected).toString();
|
|
}
|
|
async function figmaFilesQuery(options, cache) {
|
|
if (!options.token) {
|
|
throw new Error("Missing Figma API token");
|
|
}
|
|
const params = new URLSearchParams();
|
|
if (options.ids) {
|
|
params.set("ids", options.ids.join(","));
|
|
}
|
|
if (options.version) {
|
|
params.set("version", options.version);
|
|
}
|
|
if (options.depth) {
|
|
params.set("depth", options.depth.toString());
|
|
}
|
|
const queryParams = {
|
|
uri: "https://api.figma.com/v1/files/" + options.file,
|
|
params,
|
|
headers: {
|
|
"X-FIGMA-TOKEN": options.token
|
|
}
|
|
};
|
|
const isModified = async () => {
|
|
if (!cache || !options.ifModifiedSince) {
|
|
return true;
|
|
}
|
|
const cacheKey = apiCacheKey(queryParams);
|
|
const cachedData = await getAPICache(cache.dir, cacheKey);
|
|
if (!cachedData) {
|
|
return true;
|
|
}
|
|
let ifModifiedSince;
|
|
if (options.ifModifiedSince === true) {
|
|
try {
|
|
const parsedData2 = JSON.parse(cachedData);
|
|
if (typeof parsedData2.lastModified !== "string") {
|
|
await clearAPICache(cache.dir);
|
|
return true;
|
|
}
|
|
ifModifiedSince = parsedData2.lastModified;
|
|
} catch (err) {
|
|
await clearAPICache(cache.dir);
|
|
return true;
|
|
}
|
|
} else {
|
|
ifModifiedSince = options.ifModifiedSince;
|
|
}
|
|
const versionCheckParams = {
|
|
...queryParams,
|
|
params: new URLSearchParams(params)
|
|
};
|
|
versionCheckParams.params.set("depth", "1");
|
|
const data2 = await sendAPIQuery(versionCheckParams);
|
|
try {
|
|
if (typeof data2 === "string") {
|
|
const parsedData2 = JSON.parse(data2);
|
|
if (identicalDates(parsedData2.lastModified, ifModifiedSince)) {
|
|
return false;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
}
|
|
await clearAPICache(cache.dir);
|
|
return true;
|
|
};
|
|
if (!await isModified()) {
|
|
return "not_modified";
|
|
}
|
|
const data = await sendAPIQuery(queryParams, cache);
|
|
if (typeof data === "number") {
|
|
throw new Error(`Error retrieving document from API: ${data}`);
|
|
}
|
|
let parsedData;
|
|
try {
|
|
parsedData = JSON.parse(data);
|
|
} catch (err) {
|
|
throw new Error(`Error retrieving document from API: invalid data`);
|
|
}
|
|
if (typeof parsedData.status === "number") {
|
|
const figmaError = parsedData;
|
|
throw new Error(
|
|
`Error retrieving document from API: ${figmaError.err}`
|
|
);
|
|
}
|
|
const document = parsedData;
|
|
if (document.editorType !== "figma") {
|
|
throw new Error(
|
|
`Error retrieving document from API: document is for ${document.editorType}`
|
|
);
|
|
}
|
|
if (identicalDates(options.ifModifiedSince, document.lastModified)) {
|
|
return "not_modified";
|
|
}
|
|
return document;
|
|
}
|
|
async function figmaImagesQuery(options, nodes, cache) {
|
|
const uri = "https://api.figma.com/v1/images/" + options.file;
|
|
const maxLength = 2048 - uri.length;
|
|
const svgOptions = options.svgOptions || {};
|
|
const query = (ids2) => {
|
|
return new Promise((resolve, reject) => {
|
|
const params = new URLSearchParams({
|
|
ids: ids2.join(","),
|
|
format: "svg"
|
|
});
|
|
if (options.version) {
|
|
params.set("version", options.version);
|
|
}
|
|
params.set(
|
|
"svg_include_id",
|
|
svgOptions.includeID ? "true" : "false"
|
|
);
|
|
params.set(
|
|
"svg_simplify_stroke",
|
|
svgOptions.simplifyStroke ? "true" : "false"
|
|
);
|
|
params.set(
|
|
"use_absolute_bounds",
|
|
svgOptions.useAbsoluteBounds ? "true" : "false"
|
|
);
|
|
sendAPIQuery(
|
|
{
|
|
uri,
|
|
params,
|
|
headers: {
|
|
"X-FIGMA-TOKEN": options.token
|
|
}
|
|
},
|
|
cache
|
|
).then((data) => {
|
|
if (typeof data === "number") {
|
|
reject(data);
|
|
return;
|
|
}
|
|
let parsedData;
|
|
try {
|
|
parsedData = JSON.parse(data);
|
|
} catch {
|
|
reject("Bad API response");
|
|
return;
|
|
}
|
|
resolve(parsedData);
|
|
}).catch(reject);
|
|
});
|
|
};
|
|
let ids = [];
|
|
let idsLength = 0;
|
|
const allKeys = Object.keys(nodes.icons);
|
|
const queue = [];
|
|
for (let i = 0; i < allKeys.length; i++) {
|
|
const id = allKeys[i];
|
|
ids.push(id);
|
|
idsLength += id.length + 1;
|
|
if (idsLength >= maxLength) {
|
|
queue.push(ids.slice(0));
|
|
ids = [];
|
|
idsLength = 0;
|
|
}
|
|
}
|
|
if (idsLength) {
|
|
queue.push(ids.slice(0));
|
|
}
|
|
const queryParams = {
|
|
// Params
|
|
total: queue.length,
|
|
callback: (index) => query(queue[index]),
|
|
// Payload to identify failed items in onfail callback
|
|
function: "figmaImagesQuery",
|
|
payload: queue
|
|
};
|
|
const results = await runConcurrentQueries(queryParams);
|
|
let found = 0;
|
|
results.forEach((data) => {
|
|
if (!data) {
|
|
return;
|
|
}
|
|
const images = data.images;
|
|
for (const id in images) {
|
|
const node = nodes.icons[id];
|
|
const target = images[id];
|
|
if (node && target) {
|
|
node.url = target;
|
|
found++;
|
|
}
|
|
}
|
|
});
|
|
if (!found) {
|
|
throw new Error("No valid icon layers were found");
|
|
}
|
|
nodes.generatedIconsCount = found;
|
|
return nodes;
|
|
}
|
|
async function figmaDownloadImages(nodes, cache) {
|
|
const icons = nodes.icons;
|
|
const ids = Object.keys(icons);
|
|
let count = 0;
|
|
const filtered = [];
|
|
for (let i = 0; i < ids.length; i++) {
|
|
const id = ids[i];
|
|
const item = icons[id];
|
|
if (item.url) {
|
|
filtered.push(item);
|
|
}
|
|
}
|
|
const params = {
|
|
// Params
|
|
total: filtered.length,
|
|
callback: (index) => {
|
|
return new Promise((resolve, reject) => {
|
|
const item = filtered[index];
|
|
sendAPIQuery(
|
|
{
|
|
uri: item.url
|
|
},
|
|
cache
|
|
).then((data) => {
|
|
if (typeof data === "string") {
|
|
count++;
|
|
item.content = data;
|
|
resolve(void 0);
|
|
} else {
|
|
reject(data);
|
|
}
|
|
}).catch(reject);
|
|
});
|
|
},
|
|
// Payload to identify failed items in onfail callback
|
|
function: "figmaDownloadImages",
|
|
payload: filtered
|
|
};
|
|
await runConcurrentQueries(params);
|
|
if (!count) {
|
|
throw new Error("Error retrieving images");
|
|
}
|
|
nodes.downloadedIconsCount = count;
|
|
return nodes;
|
|
}
|
|
|
|
export { figmaDownloadImages, figmaFilesQuery, figmaImagesQuery };
|