Skip to content

Commit

Permalink
Merge pull request #884 from InseeFrLab:feat-share
Browse files Browse the repository at this point in the history
Feat share file
  • Loading branch information
garronej authored Nov 29, 2024
2 parents 037eb2d + c9a48eb commit d714bbf
Show file tree
Hide file tree
Showing 39 changed files with 1,699 additions and 395 deletions.
21 changes: 21 additions & 0 deletions web/src/core/tools/timeFormat/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const SECOND = 1000;

export const TIME_UNITS = {
SECOND,
MINUTE: 60 * SECOND,
HOUR: 60 * 60 * SECOND,
DAY: 24 * 60 * 60 * SECOND,
WEEK: 7 * 24 * 60 * 60 * SECOND,
MONTH: 30 * 24 * 60 * 60 * SECOND,
YEAR: 365 * 24 * 60 * 60 * SECOND
};

export const DURATION_DIVISOR_KEYS = [
"second",
"minute",
"hour",
"day",
"week",
"month",
"year"
] as const;
100 changes: 100 additions & 0 deletions web/src/core/tools/timeFormat/formatDuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { assert } from "tsafe/assert";
import { DurationTranslationFunction } from "./type";
import { TIME_UNITS, DURATION_DIVISOR_KEYS } from "./constants";

export const { formatDuration } = (() => {
const { getDurationUnits } = (() => {
type DurationUnit = {
max: number;
divisor: number;
singular: string;
plural: string;
};

function getDurationUnits(t: DurationTranslationFunction): DurationUnit[] {
return DURATION_DIVISOR_KEYS.map(divisorKey => ({
divisor: (() => {
switch (divisorKey) {
case "second":
return TIME_UNITS.SECOND;
case "minute":
return TIME_UNITS.MINUTE;
case "hour":
return TIME_UNITS.HOUR;
case "day":
return TIME_UNITS.DAY;
case "week":
return TIME_UNITS.WEEK;
case "month":
return TIME_UNITS.MONTH;
case "year":
return TIME_UNITS.YEAR;
}
})(),
max: (() => {
switch (divisorKey) {
case "second":
return TIME_UNITS.MINUTE;
case "minute":
return TIME_UNITS.HOUR;
case "hour":
return 3 * TIME_UNITS.DAY;
case "day":
return TIME_UNITS.WEEK + TIME_UNITS.DAY;
case "week":
return TIME_UNITS.MONTH;
case "month":
return TIME_UNITS.YEAR;
case "year":
return Infinity;
}
})(),
singular: t("singular", { divisorKey }),
plural: t("plural", { divisorKey })
}));
}

return { getDurationUnits };
})();

function formatDuration(params: {
durationSeconds: number;
t: DurationTranslationFunction;
}): string {
const { durationSeconds, t } = params;

for (const unit of getDurationUnits(t)) {
if (durationSeconds * 1000 < unit.max) {
const x = Math.round(durationSeconds / (unit.divisor / 1000));
return x === 1 ? unit.singular : unit.plural.replace("#", `${x}`);
}
}
assert(false);
}

return { formatDuration };
})();

export const englishDurationFormatter: DurationTranslationFunction = (key, params) => {
const en = {
singular: {
second: "1 second",
minute: "1 minute",
hour: "1 hour",
day: "1 day",
week: "1 week",
month: "1 month",
year: "1 year"
},
plural: {
second: "# seconds",
minute: "# minutes",
hour: "# hours",
day: "# days",
week: "# weeks",
month: "# months",
year: "# years"
}
};
return en[key][params.divisorKey];
};
8 changes: 8 additions & 0 deletions web/src/core/tools/timeFormat/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DURATION_DIVISOR_KEYS } from "./constants";

export type DurationDivisorKey = (typeof DURATION_DIVISOR_KEYS)[number];

export type DurationTranslationFunction = (
key: "singular" | "plural",
params: { divisorKey: DurationDivisorKey }
) => string;
102 changes: 96 additions & 6 deletions web/src/core/usecases/fileExplorer/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as s3ConfigManagement from "core/usecases/s3ConfigManagement";
import { assert } from "tsafe/assert";
import * as userAuthentication from "core/usecases/userAuthentication";
import { id } from "tsafe/id";
import type { S3Object } from "core/ports/S3Client";

const state = (rootState: RootState): State => rootState[name];

Expand Down Expand Up @@ -97,9 +98,9 @@ const currentWorkingDirectoryView = createSelector(
objects,
ongoingOperations,
s3FilesBeingUploaded
): CurrentWorkingDirectoryView | undefined => {
): CurrentWorkingDirectoryView | null => {
if (directoryPath === undefined) {
return undefined;
return null;
}
const items = objects
.map((object): CurrentWorkingDirectoryView.Item => {
Expand Down Expand Up @@ -187,6 +188,87 @@ const currentWorkingDirectoryView = createSelector(
}
);

export type ShareView = ShareView.PublicFile | ShareView.PrivateFile;

export namespace ShareView {
export type Common = {
file: S3Object.File;
};

export type PublicFile = Common & {
isPublic: true;
url: string;
};

export type PrivateFile = Common & {
isPublic: false;
validityDurationSecond: number;
validityDurationSecondOptions: number[];
url: string | undefined;
isSignedUrlBeingRequested: boolean;
};
}

const shareView = createSelector(
createSelector(state, state => state.directoryPath),
createSelector(state, state => state.objects),
createSelector(state, state => state.share),
(directoryPath, objects, share): ShareView | undefined | null => {
if (directoryPath === undefined) {
return null;
}

if (share === undefined) {
return undefined;
}

const common: ShareView.Common = {
file: (() => {
const file = objects.find(
obj => obj.basename === share.fileBasename && obj.kind === "file"
);

assert(file !== undefined);
assert(file.kind === "file");

return file;
})()
};

const isPublic = share.isSignedUrlBeingRequested === undefined;

if (isPublic) {
assert(share.url !== undefined);

return id<ShareView.PublicFile>({
...common,
isPublic: true,
url: share.url
});
}

const {
url,
isSignedUrlBeingRequested,
validityDurationSecond,
validityDurationSecondOptions
} = share;

assert(isSignedUrlBeingRequested !== undefined);
assert(validityDurationSecond !== undefined);
assert(validityDurationSecondOptions !== undefined);

return id<ShareView.PrivateFile>({
...common,
isPublic: false,
isSignedUrlBeingRequested,
url,
validityDurationSecond,
validityDurationSecondOptions
});
}
);

const isNavigationOngoing = createSelector(state, state => state.isNavigationOngoing);

const workingDirectoryPath = createSelector(
Expand All @@ -206,21 +288,25 @@ const pathMinDepth = createSelector(workingDirectoryPath, workingDirectoryPath =
});

const main = createSelector(
createSelector(state, state => state.directoryPath),
uploadProgress,
commandLogsEntries,
currentWorkingDirectoryView,
isNavigationOngoing,
pathMinDepth,
createSelector(state, state => state.viewMode),
shareView,
(
directoryPath,
uploadProgress,
commandLogsEntries,
currentWorkingDirectoryView,
isNavigationOngoing,
pathMinDepth,
viewMode
viewMode,
shareView
) => {
if (currentWorkingDirectoryView === undefined) {
if (directoryPath === undefined) {
return {
isCurrentWorkingDirectoryLoaded: false as const,
isNavigationOngoing,
Expand All @@ -231,14 +317,18 @@ const main = createSelector(
};
}

assert(currentWorkingDirectoryView !== null);
assert(shareView !== null);

return {
isCurrentWorkingDirectoryLoaded: true as const,
isNavigationOngoing,
uploadProgress,
commandLogsEntries,
pathMinDepth,
currentWorkingDirectoryView,
viewMode
viewMode,
shareView
};
}
);
Expand All @@ -260,6 +350,6 @@ const isFileExplorerEnabled = (rootState: RootState) => {

const directoryPath = createSelector(state, state => state.directoryPath);

export const protectedSelectors = { workingDirectoryPath, directoryPath };
export const protectedSelectors = { workingDirectoryPath, directoryPath, shareView };

export const selectors = { main, isFileExplorerEnabled };
Loading

0 comments on commit d714bbf

Please sign in to comment.