Skip to content

Permalinks Feature - Abel #1281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
"increment-build": "bash scripts/increment-build.sh && git add -A"
},
"dependencies": {
"@gwdevs/permalinks-hooks": "^1.0.5-beta.1",
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.5.1",
"@material-ui/lab": "4.0.0-alpha.60",
"@material-ui/styles": "^4.11.3",
"axios": "^0.21.0",
"datatable-translatable": "0.11.15",
"dcs-js": "^1.2.0-beta.1",
"deep-freeze": "^0.0.1",
"eslint-plugin-test-selectors": "^1.3.0",
"gitea-react-toolkit": "2.1.2",
Expand Down Expand Up @@ -113,4 +115,4 @@
"**/components/**.js"
]
}
}
}
2 changes: 1 addition & 1 deletion public/build_number
Original file line number Diff line number Diff line change
@@ -1 +1 @@
147-d089b2c
149-e680e09
30 changes: 18 additions & 12 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import { loadState, loadAuthentication } from './core/persistence';
import ConfirmContextProvider from './context/ConfirmContextProvider';
import { AppContextProvider } from './App.context';
import Layout from './Layout';
import usePermalinksState from './features/permalinks/usePermalinksState';
import PermalinksHandler from './features/permalinks/PermalinksHandler';

export default function App() {
const [resumedState, setResumedState] = useState();
const { permalinkState: filteredState } = usePermalinksState(resumedState);

const resumeState = useCallback(async () => {
// note that the authentication context manages its own
// state via provided persistence load and save closures
const authentication = await loadAuthentication('authentication');

const organization = authentication && (await loadState('organization'));
const language = authentication && (await loadState('language'));
const sourceRepository =
Expand All @@ -39,15 +41,19 @@ export default function App() {
resumeState();
}, [resumeState]);

const props = { ...resumedState };

return !resumedState ? (
<></>
) : (
<ConfirmContextProvider>
<AppContextProvider {...props}>
<Layout />
</AppContextProvider>
</ConfirmContextProvider>
);
const props = { ...filteredState };

return !filteredState
? (
<></>
)
: (
<ConfirmContextProvider>
<AppContextProvider {...props}>
<PermalinksHandler>
<Layout />
</PermalinksHandler>
</AppContextProvider>
</ConfirmContextProvider>
);
};
18 changes: 11 additions & 7 deletions src/components/translatable/TranslatableTSV.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
compositeKeyIndicesFromColumnNames,
generateRowId,
} from './helpers';
import useQueryOptions from '../../features/permalinks/useQueryOptions';

const delimiters = { row: '\n', cell: '\t' };

Expand Down Expand Up @@ -101,11 +102,14 @@ export default function TranslatableTSV({
return rowId;
}, [columnNames, delimiters]);

const options = {
page: 0,
rowsPerPage: 25,
rowsPerPageOptions: [10, 25, 50, 100],
};
const { options, extraColumns } = useQueryOptions({
defaultOptions: {
page: 0,
rowsPerPage: 25,
rowsPerPageOptions: [10, 25, 50, 100],
},
columnNames
});

const rowHeader = useDeepCompareCallback((rowData, actionsMenu) => {
const _props = {
Expand All @@ -124,11 +128,11 @@ export default function TranslatableTSV({
rowHeader,
compositeKeyIndices: compositeKeyIndicesFromColumnNames({ columnNames }),
columnsFilter: columnsFilterFromColumnNames({ columnNames }),
columnsShowDefault: columnsShowDefaultFromColumnNames({ columnNames }),
columnsShowDefault: [...columnsShowDefaultFromColumnNames({ columnNames }), ...extraColumns],
};

return _config;
}, [columnNames, rowHeader]);
}, [columnNames, extraColumns, rowHeader]);

return (
<ResourcesContextProvider
Expand Down
22 changes: 22 additions & 0 deletions src/features/permalinks/PermalinksHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { useContext } from 'react'
import { useDeepCompareEffect } from 'use-deep-compare';
import { useNavigation,PermalinksConfig } from '@gwdevs/permalinks-hooks';
import { AppContext } from '../../App.context';
import useFormattedLink from './useFormattedLink';
import routes from './routes.json';


export default function PermalinksHandler({ children }) {
const { state } = useContext(AppContext);

const formattedLink = useFormattedLink(state);
const { push } = useNavigation();

useDeepCompareEffect(() => {
push(formattedLink, state);
}, [formattedLink, push, state]);

return (
<PermalinksConfig routes={routes}>{children}</PermalinksConfig>
)
}
9 changes: 9 additions & 0 deletions src/features/permalinks/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function getStateKeys(state = {}) {
const { filepath, organization, sourceRepository, language } = state;
return {
organization: organization?.username,
language: language?.languageId,
resource: sourceRepository?.full_name.split('/')[1].split('_')[1],
filepath: filepath,
}
}
6 changes: 6 additions & 0 deletions src/features/permalinks/routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"entry": "project",
"path": ["organization","language","resource","...filepath"]
}
]
25 changes: 25 additions & 0 deletions src/features/permalinks/useFormattedLink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useState } from 'react';
import { useDeepCompareEffect } from 'use-deep-compare';
import useStateKeys from './useStateKeys';
import { useLocation } from '@gwdevs/permalinks-hooks';

export default function useFormattedLink({ filepath, organization, sourceRepository, language }) {
const { keys } = useStateKeys({ filepath, organization, sourceRepository, language });
const [formattedLink, setFormattedLink] = useState();
const { search } = useLocation();

useDeepCompareEffect(() => {
const entry = 'project';
const org = keys?.organization;
const lang = keys?.language;
const repo = keys?.resource;
const filepath = keys?.filepath;

const path = [org, lang, repo, filepath].filter(Boolean).join('/');
if (!!path) {
setFormattedLink(entry + '/' + path + search);
}
}, [keys,search]);

return formattedLink;
}
78 changes: 78 additions & 0 deletions src/features/permalinks/usePermalinksState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useState } from 'react';
import { useLanguages } from 'uw-languages-rcl';
import { usePermalinks } from '@gwdevs/permalinks-hooks';
import useOrgApi from '../../hooks/api/useOrgApi';
import useRepoApi from '../../hooks/api/useRepoApi';

import { getLanguage } from '../../components/languages/helpers';
import { useDeepCompareCallback, useDeepCompareEffect } from 'use-deep-compare';
import routes from './routes.json';
import useStateKeys from './useStateKeys';

export default function usePermalinksState(loadedState) {
const { permalink, isLoading: isLoadingPermalink } = usePermalinks({ routes });
const [permalinkState, setPermalinkState] = useState();

const { state: languages } = useLanguages();
const orgClient = useOrgApi();
const repoClient = useRepoApi();

const { keys: loadedKeys } = useStateKeys(loadedState);

const getPermalinkState = useDeepCompareCallback(async () => {

if (!languages?.length || !orgClient || !repoClient || !loadedState) return;

console.log('Loading app from permalink...');

const authentication = loadedState.authentication;

const organization = loadedKeys.organization === permalink.organization
? loadedState.organization
: permalink.organization && await orgClient.orgGet(permalink.organization).then(({ data }) => data).catch(err => console.warn(err))

const language = loadedKeys.language === permalink.language
? loadedState.language
: getLanguage({ languageId: permalink.language, languagesJSON: languages });

if (!language) {
alert(`Language "${permalink.language}" was not found.`);
}

const sourceRepoName = 'en_' + permalink.resource;
const sourceRepository = loadedKeys.resource === permalink.resource
? loadedState.sourceRepository
: permalink.resource && await repoClient.repoGet('unfoldingWord', sourceRepoName).then(({ data }) => {
data.tree_url = `api/v1/repos/unfoldingWord/${sourceRepoName}/git/trees/master`;
return data;
}).catch(err => console.warn(err));

const filepath = permalink.filepath;

const _permalinkState = {
authentication,
language,
sourceRepository,
filepath,
organization,
resourceLinks: null,
};
setPermalinkState(_permalinkState);
window.history.replaceState(_permalinkState,'');
}, [languages, orgClient, repoClient, permalink, loadedKeys, loadedState]);

useDeepCompareEffect(() => {

if (isLoadingPermalink && !loadedState)
return;

if (permalink && !permalinkState)
getPermalinkState();

if (!permalink && !permalinkState)
setPermalinkState(loadedState);

}, [isLoadingPermalink, permalink, loadedState, permalinkState, getPermalinkState]);

return { permalinkState, isLoading: isLoadingPermalink || (!!permalink && !permalinkState)}
}
39 changes: 39 additions & 0 deletions src/features/permalinks/useQueryOptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useState } from 'react';
import { useDeepCompareEffect } from 'use-deep-compare';
import { usePermalinks } from '@gwdevs/permalinks-hooks';

export default function useQueryOptions({defaultOptions,columnNames}) {
const { query } = usePermalinks({});
const [extraColumns, setExtraColumns] = useState([]);
const [options, setOptions] = useState(defaultOptions);

useDeepCompareEffect(() => {
const exludedFromSearch = ['columns', 'check'];

if (!query) return;

Object.keys(query).forEach((key) => {
if (!exludedFromSearch.includes(key)) {
setOptions((options) => ({ ...options, searchText: query[key] }));
}
});

},[query]);

useDeepCompareEffect(() => {
if (query && columnNames) {
const queryColumns = query.columns?.split(',');
const validColumns = columnNames.map(column => queryColumns && queryColumns.includes(column) && column) || [];

const columnsShowDefault = columnNames.map(column =>
query[column] && column
);
setExtraColumns([
...columnsShowDefault,
...validColumns,
]);
}
}, [query, columnNames]);

return {options,extraColumns}
}
17 changes: 17 additions & 0 deletions src/features/permalinks/useStateKeys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect, useState } from 'react'

export default function useStateKeys(state = {}) {
const [keys, setKeys] = useState({});
const { filepath, organization, sourceRepository, language } = state;

useEffect(() => {
setKeys({
organization: organization?.username,
language: language?.languageId,
resource: sourceRepository?.full_name.split('/')[1].split('_')[1],
filepath: filepath,
})
}, [filepath, organization?.username, sourceRepository?.full_name, language?.languageId])

return {keys, setKeys}
}
9 changes: 9 additions & 0 deletions src/hooks/api/useApiConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SERVER_URL } from '../../core/state.defaults';

const useApiConfig = ({ token, ...config }) => ({
apiKey: token ? (key) => key === "Authorization" && `token ${token}` : undefined,
basePath: SERVER_URL + "/api/v1",
...config
})

export default useApiConfig
15 changes: 15 additions & 0 deletions src/hooks/api/useOrgApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { OrganizationApi } from "dcs-js";

import useApiConfig from "./useApiConfig";

/**
* Uses DCS organization API.
* @param {string} token - Token needed to make secure requests.
*/
const useOrgApi = (config = {}) => {
const orgConfig = useApiConfig(config);
const orgClient = new OrganizationApi(orgConfig);
return orgClient;
};

export default useOrgApi;
15 changes: 15 additions & 0 deletions src/hooks/api/useRepoApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { RepositoryApi } from "dcs-js";

import useApiConfig from "./useApiConfig";

/**
* Uses DCS Repository API.
* @param {string} token - Token needed to make secure requests.
*/
const useRepoApi = (config = {}) => {
const repoConfig = useApiConfig(config);
const repoClient = new RepositoryApi(repoConfig);
return repoClient;
};

export default useRepoApi;
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,11 @@
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210"
integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==

"@gwdevs/permalinks-hooks@^1.0.5-beta.1":
version "1.0.5-beta.1"
resolved "https://registry.yarnpkg.com/@gwdevs/permalinks-hooks/-/permalinks-hooks-1.0.5-beta.1.tgz#7ef310f548ab87fc89944e9f8ea5c346bd98188b"
integrity sha512-TjPsW0cHVTZZ/qc2C2djqQWY/TeuQ+l6YK5O4QnSl/JKvFqcVZA8ANHQozWlGEuXhDtFwQic01V5RlnpDD6ktw==

"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
Expand Down Expand Up @@ -5558,6 +5563,13 @@ dayjs@^1.9.3:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==

dcs-js@^1.2.0-beta.1:
version "1.2.0-beta.1"
resolved "https://registry.yarnpkg.com/dcs-js/-/dcs-js-1.2.0-beta.1.tgz#f907ff6e5bbe6de8e81744dc657fd153c64e0c24"
integrity sha512-jxors4rKSgdQKwtkTXyhl5uuSiUC4w9Wk6sER65P2ub8tiThzmTph7vsJPRPvO+GIWmNBZopQfF91IiXqlMYjg==
dependencies:
axios "^0.21.1"

debounce-promise@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/debounce-promise/-/debounce-promise-3.1.2.tgz#320fb8c7d15a344455cd33cee5ab63530b6dc7c5"
Expand Down