diff --git a/libs/blocks/locui/actions/view.js b/libs/blocks/locui/actions/view.js
index 9396bc32df..55c2d728d0 100644
--- a/libs/blocks/locui/actions/view.js
+++ b/libs/blocks/locui/actions/view.js
@@ -19,6 +19,7 @@ import {
} from './index.js';
export default function Actions() {
+ const hasErrors = urls.value.filter((url) => !url.valid)?.length > 0;
const canAct = allowSyncToLangstore.value
|| allowSendForLoc.value
|| allowRollout.value
@@ -27,6 +28,19 @@ export default function Actions() {
const canReRollAll = languages.value.some((lang) => lang.status === 'completed');
const canRollAll = languages.value.some((lang) => lang.status === 'translated');
+ if (hasErrors) {
+ return html`
+
diff --git a/libs/blocks/locui/loc/index.js b/libs/blocks/locui/loc/index.js
index 9cc540a4be..f61a23b45a 100644
--- a/libs/blocks/locui/loc/index.js
+++ b/libs/blocks/locui/loc/index.js
@@ -23,6 +23,23 @@ const urlParams = new URLSearchParams(window.location.search);
let resourcePath;
let previewPath;
+async function validatedUrls(projectUrls) {
+ const validateUrls = [...projectUrls];
+ while (validateUrls.length) {
+ try {
+ const reqs = await Promise.all(validateUrls.splice(0, 49).map((url) => fetch(url.href)));
+ setStatus('details', 'info', 'Validating Project URLs');
+ for (const res of reqs) {
+ const projectUrl = projectUrls.find((url) => url.href === res.url);
+ projectUrl.valid = res.ok;
+ }
+ } catch (error) {
+ setStatus('details', 'error', 'There was an error validating project URLs.', error);
+ }
+ }
+ return projectUrls;
+}
+
export function getUrls(jsonUrls) {
const { locales } = getConfig();
// Assume all URLs will be the same locale as the first URL
@@ -84,9 +101,15 @@ async function loadDetails() {
return rdx;
}, []);
languages.value = projectLangs;
- urls.value = projectUrls;
+ setStatus('details', 'info', 'Validating Project Configuration');
+ urls.value = await validatedUrls(projectUrls);
if (json.settings) loadProjectSettings(json.settings.data);
- setStatus('details');
+ const errors = urls.value.filter((url) => !url.valid);
+ if (errors?.length > 0) {
+ setStatus('details', 'error', 'Invalid URLs.', errors.map((url) => (`${url.href} was not found.`)));
+ } else {
+ setStatus('details');
+ }
} catch {
setStatus('details', 'error', 'Error loading languages and URLs.');
}
@@ -99,7 +122,6 @@ async function loadHeading() {
resourcePath = json.resourcePath;
previewPath = json.preview.url;
const path = resourcePath.replace(/\.[^/.]+$/, '');
- setStatus('details');
const projectName = json.edit.name.split('.').shift().replace('-', ' ');
heading.value = { name: projectName, editUrl: json.edit.url, path };
window.document.title = `${projectName} - LocUI`;
diff --git a/libs/blocks/locui/locui.css b/libs/blocks/locui/locui.css
index 6607d88757..e9592e5b87 100644
--- a/libs/blocks/locui/locui.css
+++ b/libs/blocks/locui/locui.css
@@ -107,6 +107,7 @@ span.locui-sync-badge-mono {
background-color: #0f7c41;
height: 26px;
border-radius: 2px;
+ cursor: pointer;
}
.locui-status-toast-section {
@@ -421,6 +422,21 @@ li.locui-url {
border-bottom: 1px solid #ccc;
}
+li.locui-url.error .locui-url-path {
+ color: red;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+li.locui-url.error .locui-url-path span {
+ color: white;
+ background-color: red;
+ border-radius: 5px;
+ padding: 0 5px;
+ font-size: 12px;
+}
+
li.locui-url:last-child {
border-bottom: unset;
}
diff --git a/libs/blocks/locui/url/tabs.js b/libs/blocks/locui/url/tabs.js
index 4e79f0905e..4f82fcf538 100644
--- a/libs/blocks/locui/url/tabs.js
+++ b/libs/blocks/locui/url/tabs.js
@@ -1,4 +1,5 @@
import { html, signal, useEffect, useMemo } from '../../../deps/htm-preact.js';
+import { urls } from '../utils/state.js';
import { setActions, openWord, handleAction } from './index.js';
function useSignal(value) {
@@ -8,18 +9,20 @@ function useSignal(value) {
function Actions({ item }) {
const isExcel = item.value.path.endsWith('.json') ? ' locui-url-action-edit-excel' : ' locui-url-action-edit-word';
const isDisabled = (status) => (!status || status !== 200 ? ' disabled' : '');
+ const itemUrl = urls.value.find((url) => url.pathname === item.value.path
+ || url.langstore.pathname === item.value.path);
return html`
+ class="locui-url-action locui-url-action-edit${isExcel}${!itemUrl?.valid ? ' disabled' : ''}"
+ onClick=${(e) => { if (itemUrl.valid) openWord(e, item); }}>Edit
+ onClick=${(e) => { if (itemUrl.valid) handleAction(e, item, true); }}>Preview
+ onClick=${(e) => { if (itemUrl.valid) handleAction(e, item); }}>Live
`;
}
diff --git a/libs/blocks/locui/url/view.js b/libs/blocks/locui/url/view.js
index 7600c2a1fb..29bcd4083f 100644
--- a/libs/blocks/locui/url/view.js
+++ b/libs/blocks/locui/url/view.js
@@ -6,9 +6,9 @@ export default function Url({ suffix, item }) {
const langstorePath = item.langstore?.pathname;
return html`
-
+
Path
- ${sourcePath}
+ ${sourcePath}${!item.valid ? html`NOT FOUND` : ''}
<${Tabs} suffix=${suffix[0]} path=${sourcePath} />
${langstorePath && html`