Fix export/format ordering, fix uniform border-radius, allow listing deleted documents, fix conflicts template hook

This commit is contained in:
Simon Prévost 2023-08-24 21:51:03 -04:00
parent 85a25a44df
commit bfc502e5fa
102 changed files with 445 additions and 246 deletions

2
cli/package-lock.json generated
View File

@ -6,7 +6,7 @@
"packages": {
"": {
"name": "accent-cli",
"version": "0.13.0",
"version": "0.13.3",
"license": "MIT",
"dependencies": {
"@oclif/command": "^1.8.22",

View File

@ -12,8 +12,8 @@
"@oclif/core": "^2.3.1",
"@oclif/plugin-help": "^5.2.6",
"@oclif/plugin-not-found": "^2.3.21",
"cli-ux": "5.6.7",
"chalk": "4.1.2",
"cli-ux": "5.6.7",
"decamelize": "5.0.1",
"form-data": "^4.0.0",
"glob": "^8.1.0",

View File

@ -38,6 +38,7 @@ export default class Export extends Command {
async run() {
const {flags} = this.parse(Export);
const t0 = process.hrtime.bigint();
const documents = this.projectConfig.files();
const formatter = new DocumentExportFormatter();
@ -52,13 +53,13 @@ export default class Export extends Command {
for (const target of targets) {
const {path, language, documentPath} = target;
const localFile = document.fetchLocalFile(documentPath, path);
if (!localFile) return new Promise((resolve) => resolve(undefined));
formatter.log(localFile, documentPath);
await document.export(localFile, language, documentPath, flags);
}
formatter.done();
const t2 = process.hrtime.bigint();
formatter.footer(t2 - t0);
await new HookRunner(document).run(Hooks.afterExport);
}

View File

@ -29,7 +29,7 @@ export default class Format extends Command {
async run() {
const {flags} = this.parse(Format);
const documents = this.projectConfig.files();
const t0 = process.hrtime();
const t0 = process.hrtime.bigint();
const formattedPaths: FormattedFile[] = [];
for (const document of documents) {
@ -54,8 +54,8 @@ export default class Format extends Command {
}
}
const [, t1] = process.hrtime(t0);
const stats = {time: t1};
const t2 = process.hrtime.bigint();
const stats = {time: t2 - t0};
const formatter = new DocumentFormatter(formattedPaths, stats);

View File

@ -30,6 +30,7 @@ export default class Jipt extends Command {
async run() {
const {args} = this.parse(Jipt);
const t0 = process.hrtime.bigint();
const documents = this.projectConfig.files();
const formatter = new DocumentExportFormatter();
@ -52,6 +53,9 @@ export default class Jipt extends Command {
await document.exportJipt(path, documentPath);
}
const t2 = process.hrtime.bigint();
formatter.footer(t2 - t0);
await new HookRunner(document).run(Hooks.afterExport);
}
}

View File

@ -16,7 +16,7 @@ export default class Lint extends Command {
async run() {
const documents = this.projectConfig.files();
const results: LintTranslation[] = [];
const t0 = process.hrtime();
const t0 = process.hrtime.bigint();
for (const document of documents) {
const targets = new DocumentPathsFetcher()
@ -46,8 +46,8 @@ export default class Lint extends Command {
}
}
const [, t1] = process.hrtime(t0);
const stats = {time: t1};
const t2 = process.hrtime.bigint();
const stats = {time: t2 - t0};
const formatter = new Formatter(results, stats);

View File

@ -71,7 +71,7 @@ export default class Sync extends Command {
async run() {
const {flags} = this.parse(Sync);
const t0 = process.hrtime();
const t0 = process.hrtime.bigint();
const documents = this.projectConfig.files();
// From all the documentConfigs, do the sync or peek operations and log the results.
@ -101,8 +101,8 @@ export default class Sync extends Command {
}
if (flags['dry-run']) {
const [, t1] = process.hrtime(t0);
syncFormatter.footerDryRun(t1);
const t1 = process.hrtime.bigint();
syncFormatter.footerDryRun(t1 - t0);
return;
}
@ -119,19 +119,16 @@ export default class Sync extends Command {
for (const target of targets) {
const {path, language, documentPath} = target;
const localFile = document.fetchLocalFile(documentPath, path);
formatter.log(path, documentPath);
if (localFile) {
formatter.log(path, documentPath);
await document.export(localFile, language, documentPath, flags);
}
await document.export(localFile, language, documentPath, flags);
}
await new HookRunner(document).run(Hooks.afterExport);
}
const [, t2] = process.hrtime(t0);
syncFormatter.footer(t2);
const t2 = process.hrtime.bigint();
syncFormatter.footer(t2 - t0);
}
private async syncDocumentConfig(document: Document) {

View File

@ -14,7 +14,8 @@ export default class DocumentJiptPathsFetcher {
.map((path) => {
const parsedTarget = document.target
.replace('%slug%', pseudoLanguageName)
.replace('%original_file_name%', path);
.replace('%original_file_name%', path)
.replace('%document_path%', path);
return {
documentPath: path,

View File

@ -155,13 +155,13 @@ export default class Document {
}
fetchLocalFile(documentPath: string, localPath: string) {
return this.paths.reduce((memo: string | null, path: string) => {
return this.paths.reduce((memo: string, path: string) => {
if (this.parseDocumentName(path, this.config) === documentPath) {
return localPath;
} else {
return memo;
}
}, null);
}, localPath);
}
async export(

View File

@ -1,9 +1,8 @@
const NANOSECONDS = 1000000;
const PRECISION = 4;
const NANOSECONDS = 1000000n;
export default class Base {
formatTiming(time: number, template: (time: string) => string) {
const count = (time / NANOSECONDS).toPrecision(PRECISION);
formatTiming(time: bigint, template: (time: string) => string) {
const count = String(time / NANOSECONDS);
return template(count);
}
}

View File

@ -1,7 +1,9 @@
// Vendor
import * as chalk from 'chalk';
export default class DocumentExportFormatter {
import Base from './base';
export default class DocumentExportFormatter extends Base {
log(path: string, documentPath: string) {
console.log(
' ',
@ -10,7 +12,20 @@ export default class DocumentExportFormatter {
chalk.gray.dim.underline(path)
);
}
done() {
footer(time: bigint) {
console.log('');
console.log(
chalk.gray.dim(this.formatTime(time)),
'completed without issues'
);
console.log('');
}
formatTime(time: bigint) {
return this.formatTiming(
time,
(count) => `Exporting took ${count} milliseconds,`
);
}
}

View File

@ -5,7 +5,7 @@ import Base from './base';
// Types
interface Stats {
time: number;
time: bigint;
}
export default class DocumentFormatFormatter extends Base {
@ -34,7 +34,7 @@ export default class DocumentFormatFormatter extends Base {
console.log('');
}
formatFormattingTime(time: number) {
formatFormattingTime(time: bigint) {
return this.formatTiming(
time,
(count) => `Formatting took ${count} milliseconds`

View File

@ -8,7 +8,7 @@ const MAX_TEXT_SIZE = 30;
// Types
interface Stats {
time: number;
time: bigint;
}
export default class ProjectLintFormatter extends Base {
@ -80,7 +80,7 @@ export default class ProjectLintFormatter extends Base {
}
}
formatLintingTime(time: number) {
formatLintingTime(time: bigint) {
return this.formatTiming(
time,
(count) => `Linting took ${count} milliseconds,`

View File

@ -21,9 +21,10 @@ export default class ProjectSyncFormatter extends Base {
),
chalk.green('✓')
);
console.log('');
}
footerDryRun(time: number) {
footerDryRun(time: bigint) {
console.log('');
console.log(
chalk.gray.dim(
@ -37,7 +38,7 @@ export default class ProjectSyncFormatter extends Base {
console.log('');
}
footer(time: number) {
footer(time: bigint) {
console.log('');
console.log(
chalk.gray.dim(
@ -51,7 +52,7 @@ export default class ProjectSyncFormatter extends Base {
console.log('');
}
formatSyncingTime(time: number) {
formatSyncingTime(time: bigint) {
return this.formatTiming(
time,
(count) => `Syncing took ${count} milliseconds,`

View File

@ -68,4 +68,14 @@ defmodule Accent.Translation do
language_slug: language_slug
}
end
def maybe_natural_order_by(translations, "key") do
Enum.sort_by(translations, & &1.key)
end
def maybe_natural_order_by(translations, "-key") do
Enum.sort_by(translations, & &1.key, &>=/2)
end
def maybe_natural_order_by(translations, _), do: translations
end

View File

@ -26,8 +26,8 @@ defmodule Accent.Scopes.Document do
@doc """
Fill `translations_count`, `conflicts_count` and `reviewed_count` for documents.
"""
@spec with_stats(Ecto.Queryable.t()) :: Ecto.Queryable.t()
def with_stats(query) do
Accent.Scopes.TranslationsCount.with_stats(query, :document_id, exclude_empty_translations: true)
@spec with_stats(Ecto.Queryable.t(), Keyword.t()) :: Ecto.Queryable.t()
def with_stats(query, opts) do
Accent.Scopes.TranslationsCount.with_stats(query, :document_id, opts)
end
end

View File

@ -45,21 +45,27 @@ defmodule Accent.GraphQL.Resolvers.Document do
end
end
@spec show_project(Project.t(), %{id: String.t()}, GraphQLContext.t()) :: {:ok, Document.t() | nil}
@spec show_project(Project.t(), %{id: String.t()}, GraphQLContext.t()) ::
{:ok, Document.t() | nil}
def show_project(project, %{id: id}, _) do
Document
|> DocumentScope.from_project(project.id)
|> DocumentScope.with_stats()
|> DocumentScope.with_stats(exclude_empty_translations: true)
|> Ecto.Query.where(id: ^id)
|> Repo.one()
|> then(&{:ok, &1})
end
@spec list_project(Project.t(), %{page: number()}, GraphQLContext.t()) :: {:ok, Paginated.t(Document.t())}
@spec list_project(
Project.t(),
%{page: number(), exclude_empty_translations: boolean()},
GraphQLContext.t()
) ::
{:ok, Paginated.t(Document.t())}
def list_project(project, args, _) do
Document
|> DocumentScope.from_project(project.id)
|> DocumentScope.with_stats()
|> DocumentScope.with_stats(exclude_empty_translations: args.exclude_empty_translations)
|> Ecto.Query.order_by(desc: :updated_at)
|> Paginated.paginate(args)
|> Paginated.format()

View File

@ -79,7 +79,13 @@ defmodule Accent.GraphQL.Types.Project do
arg(:text, non_null(:string))
arg(:source_language_slug, non_null(:string))
arg(:target_language_slug, non_null(:string))
resolve(project_authorize(:machine_translations_translate, &Accent.GraphQL.Resolvers.MachineTranslation.translate_text/3))
resolve(
project_authorize(
:machine_translations_translate,
&Accent.GraphQL.Resolvers.MachineTranslation.translate_text/3
)
)
end
field :lint_translations, list_of(non_null(:lint_translation)) do
@ -87,7 +93,12 @@ defmodule Accent.GraphQL.Types.Project do
end
field :api_tokens, list_of(non_null(:api_token)) do
resolve(project_authorize(:list_project_api_tokens, &Accent.GraphQL.Resolvers.APIToken.list_project/3))
resolve(
project_authorize(
:list_project_api_tokens,
&Accent.GraphQL.Resolvers.APIToken.list_project/3
)
)
end
field :viewer_permissions, list_of(:string) do
@ -114,6 +125,7 @@ defmodule Accent.GraphQL.Types.Project do
field :documents, :documents do
arg(:page, :integer)
arg(:page_size, :integer)
arg(:exclude_empty_translations, :boolean, default_value: true)
resolve(project_authorize(:index_documents, &Accent.GraphQL.Resolvers.Document.list_project/3))
end
@ -131,7 +143,12 @@ defmodule Accent.GraphQL.Types.Project do
arg(:is_added_last_sync, :boolean)
arg(:is_commented_on, :boolean)
resolve(project_authorize(:index_translations, &Accent.GraphQL.Resolvers.Translation.list_project/3))
resolve(
project_authorize(
:index_translations,
&Accent.GraphQL.Resolvers.Translation.list_project/3
)
)
end
field :activities, :activities do
@ -142,7 +159,12 @@ defmodule Accent.GraphQL.Types.Project do
arg(:user_id, :id)
arg(:version_id, :id)
resolve(project_authorize(:index_project_activities, &Accent.GraphQL.Resolvers.Activity.list_project/3))
resolve(
project_authorize(
:index_project_activities,
&Accent.GraphQL.Resolvers.Activity.list_project/3
)
)
end
field :comments, :comments do

View File

@ -9,7 +9,7 @@ defmodule Hook.Outbounds.Discord.Templates do
"""
@new_conflicts_template """
**<%= @user %>** just added *<%= @new_conflicts_count %> strings* to review.
The project is currently **<%= @reviewed_count / @translations_count %>** reviewed (<%= @reviewed_count %>/<%= @translations_count %>)
The project is currently **<%= Float.round(@reviewed_count / @translations_count * 100, 2) %>** reviewed (<%= @reviewed_count %>/<%= @translations_count %>)
"""
@complete_review_template """
**<%= @user %>** just finished reviewing all strings!

View File

@ -9,7 +9,7 @@ defmodule Hook.Outbounds.Slack.Templates do
"""
@new_conflicts_template """
*<%= @user %>* just added _<%= @new_conflicts_count %> strings_ to review.
The project is currently *<%= @reviewed_count / @translations_count %>* reviewed (<%= @reviewed_count %>/<%= @translations_count %>)
The project is currently *<%= Float.round(@reviewed_count / @translations_count * 100, 2) %>* reviewed (<%= @reviewed_count %>/<%= @translations_count %>)
"""
@complete_review_template """
*<%= @user %>* just finished reviewing all strings!

View File

@ -21,7 +21,7 @@ defmodule Accent.MachineTranslations do
Provider.enabled?(provider)
end
@spec translate([map()], String.t(), String.t(), struct()) :: [map()]
@spec translate([map()], String.t(), String.t(), struct()) :: [map()] | {:error, any()}
def translate(entries, source_language, target_language, config) do
provider = provider_from_config(config)

View File

@ -10,28 +10,28 @@ defmodule Movement.Persisters.ProjectStateChangeWorker do
@impl Oban.Worker
def perform(%Oban.Job{args: args}) do
args = cast_args(args)
project_state = get_project_state(args.project)
current_project_state = get_project_state(args.project)
if new_conflicts_to_review?(args.previous_project_state, project_state) do
if new_conflicts_to_review?(args.previous_project_state, current_project_state) do
Hook.outbound(%Hook.Context{
event: "new_conflicts",
project_id: args.project.id,
user_id: args.user.id,
payload: %{
reviewed_count: project_state.project.reviewed_count,
translations_count: project_state.project.translations_count,
new_conflicts_count: project_state.project.translations_count - args.previous_project_state.project.translations_count
reviewed_count: current_project_state.project.reviewed_count,
translations_count: current_project_state.project.translations_count,
new_conflicts_count: current_project_state.project.conflicts_count - args.previous_project_state.project.conflicts_count
}
})
end
if all_reviewed?(args.previous_project_state, project_state) do
if all_reviewed?(args.previous_project_state, current_project_state) do
Hook.outbound(%Hook.Context{
event: "complete_review",
project_id: args.project.id,
user_id: args.user.id,
payload: %{
translations_count: project_state.project.translations_count
translations_count: current_project_state.project.translations_count
}
})
end

View File

@ -96,6 +96,7 @@ defmodule Accent.ExportController do
|> Scope.parse_added_last_sync(filters[:is_added_last_sync], revision.project_id, document && document.id)
|> Scope.parse_empty(filters[:is_text_empty])
|> Repo.all()
|> Translation.maybe_natural_order_by(order)
assign(conn, :translations, translations)
end

View File

@ -3,6 +3,8 @@ defmodule Accent.FormatController do
import Canary.Plugs
alias Accent.Translation
plug(Plug.Assign, %{canary_action: :format})
plug(:load_resource, model: Accent.Project, id_name: "project_id")
plug(Accent.Plugs.MovementContextParser)
@ -55,16 +57,7 @@ defmodule Accent.FormatController do
end
defp fetch_entries(conn, _) do
context = conn.assigns[:movement_context]
entries =
case Map.get(conn.params, "order_by") do
"-index" -> Enum.reverse(context.entries)
"key" -> Enum.sort_by(context.entries, & &1.key)
"-key" -> Enum.sort_by(context.entries, & &1.key, &>=/2)
_ -> context.entries
end
entries = Translation.maybe_natural_order_by(conn.assigns[:movement_context].entries, Map.get(conn.params, "order_by"))
assign(conn, :entries, entries)
end

View File

@ -50,6 +50,7 @@ defmodule Accent.MachineTranslationsController do
|> TranslationScope.from_version(nil)
|> TranslationScope.parse_order(conn.assigns[:order])
|> Repo.all()
|> Translation.maybe_natural_order_by(conn.assigns[:order])
|> Enum.map(&Translation.to_langue_entry(&1, nil, true, conn.assigns[:source_language].slug))
case Accent.MachineTranslations.translate(

View File

@ -85,7 +85,7 @@ defmodule AccentTest.GraphQL.Resolvers.Document do
%Translation{revision_id: revision.id, document_id: document.id, key: "ok", corrected_text: "bar", proposed_text: "bar", conflicted: false} |> Repo.insert!()
%Translation{revision_id: revision.id, document_id: other_document.id, key: "ok", corrected_text: "bar", proposed_text: "bar", conflicted: true} |> Repo.insert!()
{:ok, result} = Resolver.list_project(project, %{}, %{})
{:ok, result} = Resolver.list_project(project, %{exclude_empty_translations: true}, %{})
assert get_in(result, [:entries, Access.all(), Access.key(:id)]) == [other_document.id, document.id]
assert get_in(result, [:entries, Access.all(), Access.key(:translations_count)]) == [1, 1]
@ -100,7 +100,7 @@ defmodule AccentTest.GraphQL.Resolvers.Document do
%Document{project_id: project.id, path: "doc-#{i}", format: "json"} |> Repo.insert!()
end
{:ok, result} = Resolver.list_project(project, %{}, %{})
{:ok, result} = Resolver.list_project(project, %{exclude_empty_translations: true}, %{})
assert get_in(result, [:entries, Access.all(), Access.key(:id)]) == [document.id]
assert get_in(result, [:meta, Access.key(:total_entries)]) == 1

View File

@ -0,0 +1,9 @@
{
"challenges_bingo_action": "",
"challenge_tier_bronze": "",
"challenge_prediction_card_next_goal_description": "",
"challenges_club1909_dialog_content": "",
"challenges_club1909_dialog_title": "",
"challenges_title": "",
"close_authenticated_webview_confirm": ""
}

View File

@ -3,8 +3,12 @@ defmodule AccentTest.FormatController do
alias Accent.{
AccessToken,
Document,
Language,
Project,
Repo,
Revision,
Translation,
User
}
@ -53,4 +57,32 @@ defmodule AccentTest.FormatController do
}
"""
end
test "format order_by same as export", %{conn: conn, project: project, access_token: access_token} do
french_language = %Language{name: "french", slug: Ecto.UUID.generate()} |> Repo.insert!()
revision = %Revision{language_id: french_language.id, project_id: project.id, master: true} |> Repo.insert!()
file = %Plug.Upload{content_type: "application/json", filename: "simple.json", path: "test/support/formatter/json/ordering.json"}
body = %{inline_render: true, order_by: "key", project_id: project.id, language: french_language.slug, document_format: "json", file: file}
format_response =
conn
|> put_req_header("authorization", "Bearer #{access_token.token}")
|> post(format_path(conn, :format), body)
document = %Document{project_id: project.id, path: "ordering", format: "json"} |> Repo.insert!()
params = %{order_by: "key", project_id: project.id, language: french_language.slug, document_format: "json", document_path: "ordering"}
content = Jason.decode!(File.read!(file.path))
for {key, value} <- content do
%Translation{revision_id: revision.id, key: key, corrected_text: value, proposed_text: value, document_id: document.id} |> Repo.insert!()
end
export_response =
conn
|> put_req_header("authorization", "Bearer #{access_token.token}")
|> get(export_path(conn, [], params))
assert format_response.resp_body == export_response.resp_body
end
end

View File

@ -217,6 +217,9 @@
},
"documents_list": {
"empty_text": "Files are the actual listing of your projects master language files. Add them here or from the CLI and keep them in sync with your project.",
"export_all": "Export all",
"show_deleted": "Show deleted",
"hide_deleted": "Hide deleted",
"format": "Format",
"review": "In review",
"total": "Total",
@ -232,6 +235,7 @@
"save_button": "Save",
"cancel_button": "Cancel",
"item": {
"removed_label": "Removed",
"deleting_label": "Deleting {path}.{extension}…",
"path_label": "Path",
"path_help": "Used to scope your strings. The path will be used to export all your languages (en/<em>path</em>.format, fr/<em>path</em>.format)"
@ -559,7 +563,7 @@
"remove": "Delete string",
"merge": "Add translations to files",
"sync": "Sync files",
"update_proposed": "Update uploaded text reference:"
"update_proposed": "Update uploaded text reference"
},
"action_text": {
"add_to_version": "froze the string in a version:",

View File

@ -234,6 +234,9 @@
"documents_list": {
"empty_text": "Les fichiers sont la liste réelle des fichiers de langue principaux de votre projet. Ajoutez-les ici ou depuis la CLI et gardez-les synchronisés avec votre projet.",
"format": "Format",
"export_all": "Tout exporter",
"show_deleted": "Voir les fichiers supprimés",
"hide_deleted": "Filtrer les fichiers supprimés",
"review": "À réviser",
"total": "Le total",
"sync": "Synchroniser",
@ -248,6 +251,7 @@
"save_button": "Sauver",
"cancel_button": "Annuler",
"item": {
"removed_label": "Supprimé",
"deleting_label": "Suppression de {path}.{extension}…",
"path_label": "Chemin",
"path_help": "Utilisé pour délimiter vos chaînes. Le chemin sera utilisé pour exporter toutes vos langues (en/<em>path</em>.format, fr/<em>path</em>.format)"

View File

@ -5,7 +5,7 @@
align-items: center;
padding: 1px 6px 1px 5px;
background: var(--background-light);
border-radius: 3px;
border-radius: var(--border-radius);
color: #949494;
font-size: 11px;
font-weight: 600;

View File

@ -3,7 +3,7 @@
margin-bottom: 10px;
width: 400px;
background: #fff;
border-radius: 3px;
border-radius: var(--border-radius);
box-shadow: 0 1px 12px var(--shadow-color);
text-shadow: 0 1px 1px rgba(#000, 0.1);
color: #fff;

View File

@ -35,7 +35,7 @@
z-index: 3;
max-width: 900px;
width: 100%;
border-radius: 4px;
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: 0 2px 20px 0 var(--shadow-color);
background: var(--content-background);

View File

@ -9,7 +9,7 @@
padding: 5px 25px 5px 5px;
background: var(--content-background);
box-shadow: none;
border-radius: 3px;
border-radius: var(--border-radius);
border: 1px solid transparent;
border-color: var(--background-light-highlight);
color: var(--color-black-opacity-70);

View File

@ -2,7 +2,7 @@
width: 100%;
max-width: 1200px;
margin: 40px auto;
border-radius: 3px;
border-radius: var(--border-radius);
border: 1px solid var(--content-background-border);
overflow: hidden;
background: var(--content-background);

View File

@ -42,8 +42,14 @@
.item.batch-sync,
.item.sync {
position: relative;
transform: translateX(-1px);
padding: 6px 10px 5px;
border-color: transparent;
border-left: 1px solid var(--color-primary-darken-10);
&.compact {
border-left: 1px solid var(--color-primary-darken-10);
}
&:before {
display: block;
@ -72,7 +78,7 @@
.item-stats {
padding: 0;
background: transparent;
color: var(--color-primary-darken-50);
color: var(--text-color-normal);
}
.item-date {
@ -85,6 +91,7 @@
.item-content {
background: var(--color-primary-lighten-95);
color: var(--color-primary-darken-50);
}
.item-documentPath {
@ -390,7 +397,7 @@
text-decoration: none;
background: var(--background-tooltip);
padding: 1px 3px;
border-radius: 3px;
border-radius: var(--border-radius);
&:focus,
&:hover {
@ -463,7 +470,7 @@
width: 17px;
height: 17px;
margin-right: 5px;
border-radius: 3px;
border-radius: var(--border-radius);
}
.item-translationFromOperationText,
@ -613,8 +620,8 @@ a.item-documentPath {
}
.item-stats {
font-size: 14px;
list-style: none;
border-radius: var(--border-radius);
}
.item-rollback {

View File

@ -31,14 +31,14 @@
.select {
position: relative;
font-size: 11px;
select {
font-size: 11px;
appearance: none;
border: 1px solid var(--background-light-highlight);
background: var(--background-light);
color: var(--text-color-normal);
border-radius: 3px;
border-radius: var(--border-radius);
padding: 2px 15px 2px 4px;
}
}

View File

@ -28,7 +28,7 @@
display: inline-block;
margin-bottom: 7px;
padding: 2px 0 3px;
border-radius: 4px;
border-radius: var(--border-radius);
color: var(--color-primary);
font-size: 11px;

View File

@ -5,7 +5,7 @@
.conflict-item {
padding: 10px;
margin-bottom: 15px;
border-radius: 3px;
border-radius: var(--border-radius);
&:nth-child(even) {
background: var(--background-light);

View File

@ -7,7 +7,7 @@
max-width: 50%;
margin-bottom: 20px;
margin-right: 20px;
border-radius: 3px;
border-radius: var(--border-radius);
border: 1px solid var(--background-light-highlight);
&:nth-child(even) {
@ -91,7 +91,7 @@
.reviewedStats {
padding: 2px 0 1px 7px;
border-radius: 3px;
border-radius: var(--border-radius);
color: var(--color-grey);
font-size: 12px;
font-family: var(--font-monospace);

View File

@ -112,7 +112,7 @@
padding: 15px;
background: var(--content-background);
box-shadow: 0 1px 4px var(--shadow-color), 0 7px 12px var(--shadow-color);
border-radius: 2px;
border-radius: var(--border-radius);
color: var(--color-primary);
text-decoration: none;
transition: box-shadow 0.2s ease-in-out;

View File

@ -1,5 +0,0 @@
a.button {
position: absolute;
right: 36px;
top: 8px;
}

View File

@ -1,4 +0,0 @@
<LinkTo @route='logged-in.project.files.export-all' @model={{@project.id}} local-class='button' class='button button--white button--filled'>
{{inline-svg '/assets/export.svg' class='button-icon'}}
{{t 'components.project_file_operations.export_all'}}
</LinkTo>

View File

@ -8,4 +8,15 @@ interface Args {
onUpdate: (documentEntity: any, path: string) => Promise<void>;
}
export default class DocumentsList extends Component<Args> {}
export default class DocumentsList extends Component<Args> {
get documents() {
const emptyDocuments = this.args.documents.filter(
(document) => document.translationsCount === 0
);
const documents = this.args.documents.filter(
(document) => document.translationsCount !== 0
);
return [...documents, ...emptyDocuments];
}
}

View File

@ -56,6 +56,14 @@ export default class DocumentsListItem extends Component<Args> {
});
}
get empty() {
return this.args.document.translationsCount === 0;
}
get showStats() {
return !this.empty && !this.isEditing;
}
get correctedKeysPercentage() {
return percentage(
this.args.document.translationsCount - this.args.document.conflictsCount,

View File

@ -11,6 +11,26 @@
}
}
.documents-list-item.empty {
padding: 0 6px 4px;
margin-left: -6px;
margin-bottom: 10px;
width: calc(100% - 6px);
background: var(--body-background);
border-radius: var(--border-radius);
&.editing {
padding: 0;
background: transparent;
border-radius: 0;
margin-left: 0;
.removed-badge {
display: none;
}
}
}
.documents-list-item.low-percentage {
.reviewedPercentage {
color: var(--color-error);
@ -76,9 +96,13 @@
.item-title {
display: inline-flex;
width: 100%;
margin-bottom: 7px;
font-size: 15px;
cursor: pointer;
margin-left: -2px;
}
.removed-badge {
font-size: 11px;
opacity: 0.4;
}
.item-subtitle {
@ -150,7 +174,7 @@
margin: 0 2px;
padding: 0 2px;
border: 1px solid var(--background-light-border);
border-radius: 3px;
border-radius: var(--border-radius);
background: var(--background-light);
font-family: var(--font-monospace);
font-style: normal;

View File

@ -1,6 +1,17 @@
<li local-class='documents-list-item {{if this.lowPercentage "low-percentage"}} {{if this.mediumPercentage "medium-percentage"}} {{if this.highPercentage "high-percentage"}}'>
<li
local-class='documents-list-item {{if this.isEditing "editing"}} {{if this.empty "empty"}} {{if this.lowPercentage "low-percentage"}} {{if
this.mediumPercentage
"medium-percentage"
}} {{if this.highPercentage "high-percentage"}}'
>
<form local-class='item-form {{if this.isDeleting "item-form--deleting"}}' {{on 'submit' (fn this.updateDocument)}}>
<div local-class='item-form-content'>
{{#if this.empty}}
<span local-class='removed-badge'>
{{t 'components.documents_list.item.removed_label'}}
</span>
{{/if}}
<div local-class='item-form-inputs'>
<h2 local-class='item-title'>
{{#if this.isEditing}}
@ -35,7 +46,7 @@
</h2>
</div>
{{#unless this.isEditing}}
{{#if this.showStats}}
<div local-class='stat'>
<span local-class='reviewedPercentage'>
{{this.correctedKeysPercentage}}
@ -55,7 +66,7 @@
<div local-class='progress'>
<ReviewProgressBar @correctedKeysPercentage={{this.correctedKeysPercentage}} />
</div>
{{/unless}}
{{/if}}
</div>
{{#if this.isEditing}}
@ -68,63 +79,65 @@
</button>
</div>
{{else}}
<div local-class='links'>
{{#if (get @permissions 'sync')}}
<LinkTo
@route='logged-in.project.files.sync'
@models={{array @project.id @document.id}}
local-class='button-sync'
title={{t 'components.documents_list.sync'}}
class='tooltip tooltip--top button button--filled button--white button--iconOnly'
>
{{inline-svg '/assets/sync.svg' class='button-icon'}}
</LinkTo>
{{/if}}
{{#if (get @permissions 'merge')}}
<LinkTo
@route='logged-in.project.files.add-translations'
@models={{array @project.id @document.id}}
title={{t 'components.documents_list.merge'}}
class='tooltip tooltip--top button button--filled button--white button--iconOnly'
>
{{inline-svg '/assets/merge.svg' class='button-icon'}}
</LinkTo>
{{/if}}
{{#if (get @permissions 'machine_translations_translate')}}
<LinkTo
@route='logged-in.project.files.machine-translations'
@models={{array @project.id @document.id}}
title={{t 'components.documents_list.machine_translations'}}
class='tooltip tooltip--top button button--filled button--white button--iconOnly'
>
{{inline-svg '/assets/language.svg' class='button-icon'}}
</LinkTo>
{{/if}}
<LinkTo
@route='logged-in.project.files.export'
@models={{array @project.id @document.id}}
title={{t 'components.documents_list.export'}}
class='tooltip tooltip--top button button--filled button--white button--iconOnly'
>
{{inline-svg '/assets/export.svg' class='button-icon'}}
</LinkTo>
</div>
<div local-class='deleteDocumentButton-container'>
{{#if (get @permissions 'delete_document')}}
{{#if this.canDeleteFile}}
<AsyncButton
@onClick={{fn this.deleteFile @document}}
@loading={{this.isDeleting}}
title={{t 'components.documents_list.delete_document'}}
class='tooltip tooltip--top button button--small button--red button--borderless button--iconOnly'
local-class='deleteDocumentButton'
{{#unless this.empty}}
<div local-class='links'>
{{#if (get @permissions 'sync')}}
<LinkTo
@route='logged-in.project.files.sync'
@models={{array @project.id @document.id}}
local-class='button-sync'
title={{t 'components.documents_list.sync'}}
class='tooltip tooltip--top button button--filled button--white button--iconOnly'
>
{{inline-svg '/assets/x.svg' class='button-icon'}}
</AsyncButton>
{{inline-svg '/assets/sync.svg' class='button-icon'}}
</LinkTo>
{{/if}}
{{/if}}
</div>
{{#if (get @permissions 'merge')}}
<LinkTo
@route='logged-in.project.files.add-translations'
@models={{array @project.id @document.id}}
title={{t 'components.documents_list.merge'}}
class='tooltip tooltip--top button button--filled button--white button--iconOnly'
>
{{inline-svg '/assets/merge.svg' class='button-icon'}}
</LinkTo>
{{/if}}
{{#if (get @permissions 'machine_translations_translate')}}
<LinkTo
@route='logged-in.project.files.machine-translations'
@models={{array @project.id @document.id}}
title={{t 'components.documents_list.machine_translations'}}
class='tooltip tooltip--top button button--filled button--white button--iconOnly'
>
{{inline-svg '/assets/language.svg' class='button-icon'}}
</LinkTo>
{{/if}}
<LinkTo
@route='logged-in.project.files.export'
@models={{array @project.id @document.id}}
title={{t 'components.documents_list.export'}}
class='tooltip tooltip--top button button--filled button--white button--iconOnly'
>
{{inline-svg '/assets/export.svg' class='button-icon'}}
</LinkTo>
</div>
<div local-class='deleteDocumentButton-container'>
{{#if (get @permissions 'delete_document')}}
{{#if this.canDeleteFile}}
<AsyncButton
@onClick={{fn this.deleteFile @document}}
@loading={{this.isDeleting}}
title={{t 'components.documents_list.delete_document'}}
class='tooltip tooltip--top button button--small button--red button--borderless button--iconOnly'
local-class='deleteDocumentButton'
>
{{inline-svg '/assets/x.svg' class='button-icon'}}
</AsyncButton>
{{/if}}
{{/if}}
</div>
{{/unless}}
{{/if}}
</form>
</li>

View File

@ -1,5 +1,5 @@
<ul local-class='documents-list'>
{{#each @documents key='id' as |document|}}
{{#each this.documents key='id' as |document|}}
<DocumentsList::Item @permissions={{@permissions}} @document={{document}} @onDelete={{@onDelete}} @onUpdate={{@onUpdate}} @project={{@project}} />
{{else}}
<div local-class='empty-content'>

View File

@ -6,7 +6,7 @@
font-size: 13px;
font-weight: 300;
background-color: var(--color-primary-opacity-10);
border-radius: 3px;
border-radius: var(--border-radius);
line-height: 1.5;
&.empty-content--center {

View File

@ -29,7 +29,7 @@ button.button {
opacity: 0.6;
padding: 5px 0;
margin-bottom: 12px;
border-radius: 3px;
border-radius: var(--border-radius);
}
.result-text {
@ -38,7 +38,7 @@ button.button {
background: var(--color-primary-opacity-10);
padding: 7px 10px;
margin-top: 12px;
border-radius: 3px;
border-radius: var(--border-radius);
}
.prompt-button {

View File

@ -12,7 +12,7 @@
display: flex;
align-items: center;
padding: 5px 9px 5px 6px;
border-radius: 3px;
border-radius: var(--border-radius);
color: var(--color-warning);
font-weight: 500;
font-size: 12px;

View File

@ -148,7 +148,7 @@ a.loginButton {
border: 1px solid var(--content-background-border);
color: var(--input-color);
text-shadow: none;
border-radius: 4px;
border-radius: var(--border-radius);
margin-top: 5px;
margin-bottom: 25px;
padding-top: 7px;

View File

@ -14,7 +14,7 @@
border: 1px solid var(--background-light-highlight);
background: var(--content-background);
padding: 4px 8px;
border-radius: 3px;
border-radius: var(--border-radius);
}
}

View File

@ -12,7 +12,7 @@
border: 1px solid var(--background-light-highlight);
background: var(--content-background);
padding: 4px 8px;
border-radius: 3px;
border-radius: var(--border-radius);
}
}

View File

@ -0,0 +1,7 @@
.wrapper {
position: absolute;
top: 6px;
right: 0;
display: flex;
gap: 10px;
}

View File

@ -0,0 +1,3 @@
<div local-class='wrapper'>
{{yield}}
</div>

View File

@ -1,7 +1,7 @@
:global(.filters).filters {
margin-top: 20px;
border: 1px solid var(--content-background-border);
border-radius: 3px;
border-radius: var(--border-radius);
box-shadow: none;
}

View File

@ -15,7 +15,7 @@
font-weight: 300;
font-size: 13px;
color: #fff;
border-radius: 3px;
border-radius: var(--border-radius);
}
.activity-explanation-label {
@ -85,7 +85,7 @@
margin-right: 20px;
border: 1px solid var(--content-background-border);
padding: 10px;
border-radius: 3px;
border-radius: var(--border-radius);
background: var(--content-background);
}
@ -94,7 +94,7 @@
padding: 13px;
background: var(--content-background);
box-shadow: 0 1px 2px var(--shadow-color), 0 5px 10px var(--shadow-color);
border-radius: 3px;
border-radius: var(--border-radius);
}
.translation-state-item,

View File

@ -3,7 +3,7 @@
}
.translationCommentsList {
border-radius: 3px;
border-radius: var(--border-radius);
}
.item-link {

View File

@ -27,7 +27,7 @@
width: 18px;
height: 18px;
margin-right: 4px;
border-radius: 3px;
border-radius: var(--border-radius);
}
.username {
@ -55,7 +55,7 @@
transition: 0.2s ease-in-out;
transition-property: padding, background, border, border-radius, box-shadow;
border: 1px solid var(--input-border-color);
border-radius: 3px;
border-radius: var(--border-radius);
background: var(--body-background);
color: var(--text-color-normal);
@ -66,7 +66,7 @@
&:focus {
outline: none;
border-color: var(--input-border-color);
border-radius: 3px;
border-radius: var(--border-radius);
border: 1px solid var(--input-border-color);
background: var(--input-background);
padding-right: 40px;

View File

@ -43,24 +43,18 @@
text-decoration: none;
font-weight: 600;
font-size: 14px;
border-radius: 4px;
opacity: 0.6;
border-radius: var(--border-radius);
margin-bottom: 4px;
color: var(--text-color-normal);
&:hover,
&:focus {
opacity: 1;
color: var(--color-primary);
.list-item-link-icon {
transform: scale(1);
opacity: 1;
}
.list-item-link-text {
color: var(--color-primary);
}
}
&:global(.active) {
@ -83,7 +77,7 @@
.list-item-link-text {
width: 127px;
padding: 0 0 0 10px;
opacity: 0.8;
opacity: 0.7;
}
.list-item-link-icon {
@ -92,7 +86,7 @@
display: inline-block;
width: 15px;
height: 15px;
opacity: 0.6;
opacity: 0.4;
}
@media (max-width: 800px) {

View File

@ -22,7 +22,7 @@
opacity: 0.6;
align-items: center;
margin: 10px 0;
border-radius: 3px;
border-radius: var(--border-radius);
text-decoration: none;
padding: 5px 0;
font-size: 11px;

View File

@ -23,7 +23,7 @@
.user-token {
width: 100%;
padding: 20px;
border-radius: 3px;
border-radius: var(--border-radius);
background-color: var(--background-light);
}
@ -108,7 +108,7 @@
.api-token-permission {
padding: 1px 3px;
border-radius: 3px;
border-radius: var(--border-radius);
background-color: var(--background-light);
border: 1px solid var(--background-light-highlight);
font-size: 11px;
@ -182,7 +182,7 @@
width: 14px;
height: 14px;
object-fit: cover;
border-radius: 3px;
border-radius: var(--border-radius);
}
.form {
@ -192,7 +192,7 @@
gap: 8px;
margin-top: 36px;
padding: 16px;
border-radius: 4px;
border-radius: var(--border-radius);
background: var(--background-light);
border: 1px solid var(--content-background-border);
}

View File

@ -37,7 +37,7 @@
font-size: 11px;
background: var(--background-light);
border: 2px solid var(--background-light-highlight);
border-radius: 3px;
border-radius: var(--border-radius);
transition-property: border-color, box-shadow;
transition: 0.3s ease-in-out;
color: var(--color-black);

View File

@ -53,7 +53,7 @@
padding: 5px 7px 4px;
margin-bottom: 10px;
border: 1px solid var(--content-background-border);
border-radius: 3px;
border-radius: var(--border-radius);
background: var(--content-background);
font-size: 12px;
box-shadow: 0 1px 5px var(--shadow-color);
@ -84,7 +84,7 @@
height: 17px;
margin-right: 6px;
margin-top: 3px;
border-radius: 3px;
border-radius: var(--border-radius);
}
.invite {

View File

@ -14,7 +14,7 @@
.zone {
margin-top: 12px;
padding: 10px;
border-radius: 3px;
border-radius: var(--border-radius);
background: var(--background-light);
color: var(--color-error);
}

View File

@ -118,7 +118,7 @@ div.lock-text {
justify-content: center;
padding: 0 10px;
height: 40px;
border-radius: 4px;
border-radius: var(--border-radius);
border: 2px solid var(--input-border-color);
background: var(--input-background);
}

View File

@ -7,7 +7,7 @@
margin-right: 10px;
border: 1px solid var(--background-light-highlight);
padding: 4px 6px;
border-radius: 3px;
border-radius: var(--border-radius);
background: var(--input-background);
cursor: pointer;
transition: 0.2s ease-in-out;

View File

@ -2,7 +2,7 @@
padding: 10px 15px;
border: 1px solid var(--background-light-highlight);
background: var(--background-light);
border-radius: 3px;
border-radius: var(--border-radius);
}
.form {

View File

@ -8,7 +8,7 @@
align-items: center;
padding: 10px 15px;
border: 1px solid var(--background-light-highlight);
border-radius: 3px;
border-radius: var(--border-radius);
}
.details-info {

View File

@ -29,7 +29,7 @@
font-family: var(--font-monospace);
font-size: 15px;
border: 2px solid var(--background-light-highlight);
border-radius: 3px;
border-radius: var(--border-radius);
color: var(--text-color-normal);
transition-property: border-color, box-shadow;
transition: 0.3s ease-in-out;

View File

@ -17,7 +17,7 @@
margin: 0 30px 30px 0;
max-width: 275px;
background: var(--content-background);
border-radius: 3px;
border-radius: var(--border-radius);
color: var(--color-black);
box-shadow: 0 1px 4px var(--shadow-color), 0 9px 19px var(--shadow-color);
text-decoration: none;

View File

@ -6,7 +6,7 @@
.form {
margin-top: 25px;
padding: 16px;
border-radius: 4px;
border-radius: var(--border-radius);
background: var(--background-light);
border: 1px solid var(--background-light-highlight);

View File

@ -1,7 +1,7 @@
.form {
margin-bottom: 25px;
padding: 12px;
border-radius: 3px;
border-radius: var(--border-radius);
background: var(--background-light);
border: 1px solid var(--background-light-highlight);

View File

@ -36,7 +36,7 @@
padding: 10px;
background: var(--background-light);
border: 1px solid var(--background-light-highlight);
border-radius: 3px;
border-radius: var(--border-radius);
color: var(--color-black);
}

View File

@ -1,7 +1,7 @@
.form {
margin-top: 25px;
padding: 16px;
border-radius: 4px;
border-radius: var(--border-radius);
background: var(--background-light);
border: 1px solid var(--background-light-highlight);

View File

@ -1,6 +1,6 @@
.wrapper {
padding: 10px 90px 10px 10px;
border-radius: 4px;
border-radius: var(--border-radius);
border: 1px solid var(--content-background-border);
font-size: 14px;
position: relative;
@ -24,6 +24,6 @@
.quick-access {
padding: 0 4px;
border-radius: 3px;
border-radius: var(--border-radius);
display: block;
}

View File

@ -63,7 +63,7 @@
width: 18px;
height: 18px;
margin-right: 4px;
border-radius: 3px;
border-radius: var(--border-radius);
}
.username {

View File

@ -32,7 +32,7 @@
padding: 12px 15px;
transition: 0.2s ease-in-out;
transition-property: box-shadow;
border-radius: 3px;
border-radius: var(--border-radius);
border: 1px solid var(--content-background-border);
background: var(--content-background);
color: var(--color-black);

View File

@ -25,7 +25,7 @@
padding: 4px 10px 5px;
margin-right: 12px;
background: var(--background-light);
border-radius: 3px;
border-radius: var(--border-radius);
text-decoration: none;
font-weight: 600;
box-shadow: 0 1px 2px var(--shadow-color);

View File

@ -5,7 +5,7 @@
select {
padding: 10px 10px 10px 7px;
padding-bottom: 25px;
border-radius: 3px;
border-radius: var(--border-radius);
font-size: 20px;
flex-direction: row-reverse;
cursor: pointer;
@ -28,7 +28,7 @@
width: 100%;
pointer-events: none;
z-index: 10;
border-radius: 3px;
border-radius: var(--border-radius);
background: var(--content-background);
border: 1px solid var(--content-background-border);

View File

@ -3,7 +3,7 @@
height: 2px;
width: 100%;
margin: 0;
border-radius: 4px;
border-radius: var(--border-radius);
}
.progress-line {

View File

@ -46,7 +46,7 @@
width: 16px;
height: 16px;
margin-right: 6px;
border-radius: 3px;
border-radius: var(--border-radius);
}
.date {

View File

@ -2,7 +2,7 @@
position: relative;
box-shadow: 0 1px 4px var(--shadow-color), 0 9px 19px var(--shadow-color);
background: var(--background-light);
border-radius: 3px;
border-radius: var(--border-radius);
}
.translation-comments-list.translationRemoved {

View File

@ -6,7 +6,7 @@
align-items: center;
padding: 10px;
margin-bottom: 3px;
border-radius: 3px;
border-radius: var(--border-radius);
font-weight: bold;
font-size: 13px;
color: #444;
@ -46,7 +46,7 @@
.label {
display: inline-block;
padding: 5px 8px 4px;
border-radius: 3px;
border-radius: var(--border-radius);
margin-bottom: 5px;
background: hsl(
var(--color-blue-hue),
@ -78,7 +78,7 @@
.placeholders {
margin: 0 0 15px;
padding: 15px;
border-radius: 3px;
border-radius: var(--border-radius);
font-size: 12px;
background: hsl(
var(--color-blue-hue),
@ -135,7 +135,7 @@
.file-comment {
margin: 0 0 15px;
padding: 15px 15px 5px;
border-radius: 3px;
border-radius: var(--border-radius);
font-size: 12px;
background: var(--background-light);
color: var(--color-black);

View File

@ -54,7 +54,7 @@ button.advancedFilters {
top: -4px;
right: -7px;
background: var(--color-primary);
border-radius: 2px;
border-radius: var(--border-radius);
padding: 0 4px 1px;
color: #fff;
font-size: 10px;

View File

@ -16,6 +16,18 @@
<div class='queryForm-filters'>
<div class='queryForm-filters-column'>
{{#if @onChangeAdvancedFilterBoolean}}
<button {{on 'click' (fn this.toggleAdvancedFilters)}} local-class='advancedFilters' class='button button--filled button--white'>
{{inline-svg 'assets/filter.svg' class='button-icon'}}
{{t 'components.translations_filter.advanced_filters_button'}}
{{#if @withAdvancedFilters}}
<span local-class='advancedFilters-badge'>
{{@withAdvancedFilters}}
</span>
{{/if}}
</button>
{{/if}}
{{#if this.showDocumentsSelect}}
<div class='queryForm-filter'>
<div class='queryForm-filter-select'>
@ -43,18 +55,6 @@
</div>
</div>
{{/if}}
{{#if @onChangeAdvancedFilterBoolean}}
<button {{on 'click' (fn this.toggleAdvancedFilters)}} local-class='advancedFilters' class='button button--filled button--white'>
{{inline-svg 'assets/filter.svg' class='button-icon'}}
{{t 'components.translations_filter.advanced_filters_button'}}
{{#if @withAdvancedFilters}}
<span local-class='advancedFilters-badge'>
{{@withAdvancedFilters}}
</span>
{{/if}}
</button>
{{/if}}
</div>
</div>

View File

@ -5,7 +5,7 @@
margin: 8px 0 4px;
padding: 10px;
border: 1px solid transparent;
border-radius: 3px;
border-radius: var(--border-radius);
&:focus,
&:hover {

View File

@ -40,7 +40,7 @@
text-decoration: none;
background: var(--background-tooltip);
padding: 1px 3px;
border-radius: 3px;
border-radius: var(--border-radius);
}
.item-meta {

View File

@ -52,7 +52,7 @@
margin-bottom: 10px;
margin-right: 10px;
box-shadow: 0 4px 8px var(--shadow-color), 0 5px 15px var(--shadow-color);
border-radius: 4px;
border-radius: var(--border-radius);
color: var(--color-primary);
text-decoration: none;
transition: 0.2s ease-in-out;

View File

@ -48,6 +48,7 @@ export default class ProjectController extends Controller {
--color-primary-hue: ${color.hue()};
--color-primary-saturation: ${color.saturationv()}%;
--color-primary-darken-10: ${color.darken(0.1).string()};
--color-primary-darken-50: ${color.darken(0.5).string()};
--color-primary-opacity-10: ${color.fade(0.9).string()};
--color-primary-opacity-50: ${color.fade(0.5).string()};
--color-primary-opacity-70: ${color.fade(0.3).string()};

View File

@ -35,6 +35,9 @@ export default class FilesController extends Controller {
@tracked
page = 1;
@tracked
excludeEmptyTranslations = true;
@equal('model.documents', undefined)
emptyEntries: boolean;
@ -79,6 +82,11 @@ export default class FilesController extends Controller {
}
}
@action
toggleExcludeEmptyTranslations() {
this.excludeEmptyTranslations = !this.excludeEmptyTranslations;
}
@action
selectPage(page: number) {
window.scroll(0, 0);

View File

@ -20,11 +20,20 @@ export default class FilesRoute extends Route {
page: {
refreshModel: true,
},
excludeEmptyTranslations: {
refreshModel: true,
},
};
subscription: Subscription;
model({page}: {page: string}, transition: Transition) {
model(
{
page,
excludeEmptyTranslations,
}: {page: string; excludeEmptyTranslations: boolean},
transition: Transition
) {
const pageNumber = page ? parseInt(page, 10) : null;
this.subscription = this.apolloSubscription.graphql(
@ -41,6 +50,7 @@ export default class FilesRoute extends Route {
variables: {
projectId: this.routeParams.fetch(transition, 'logged-in.project')
.projectId,
excludeEmptyTranslations,
page: pageNumber,
},
},

View File

@ -2,7 +2,22 @@
{{inline-svg '/assets/file.svg'}}
<h1>{{t 'components.page_title.files'}}</h1>
<DocumentsExportAll @project={{this.model.project}} />
<PageTitle::End>
<LinkTo @route='logged-in.project.files.export-all' @model={{this.model.project.id}} class='button button--white button--filled'>
{{inline-svg '/assets/export.svg' class='button-icon'}}
{{t 'components.documents_list.export_all'}}
</LinkTo>
<button class='button {{if this.excludeEmptyTranslations "button--grey" "button--white"}} button--filled' {{on 'click' this.toggleExcludeEmptyTranslations}}>
{{#if this.excludeEmptyTranslations}}
{{inline-svg '/assets/eye.svg' class='button-icon'}}
{{t 'components.documents_list.show_deleted'}}
{{else}}
{{inline-svg '/assets/eye.svg' class='button-icon'}}
{{t 'components.documents_list.hide_deleted'}}
{{/if}}
</button>
</PageTitle::End>
</PageTitle>
{{#if this.model.loading}}

View File

@ -1,7 +1,11 @@
import gql from 'graphql-tag';
export default gql`
query ProjectDocuments($projectId: ID!, $page: Int) {
query ProjectDocuments(
$projectId: ID!
$page: Int
$excludeEmptyTranslations: Boolean
) {
viewer {
project(id: $projectId) {
id
@ -25,7 +29,10 @@ export default gql`
}
}
documents(page: $page) {
documents(
page: $page
excludeEmptyTranslations: $excludeEmptyTranslations
) {
meta {
totalEntries
totalPages

View File

@ -171,7 +171,7 @@ html {
--screen-md: 640px;
--screen-sm: 440px;
--border-radius: 3px;
--border-radius: 6px;
--accent-hue: 155;
--accent-saturation: 67%;
@ -336,7 +336,7 @@ input[type='radio'] {
}
input[type='checkbox'] {
border-radius: 3px;
border-radius: var(--border-radius);
}
input[type='radio'] {
@ -404,7 +404,7 @@ input[type='radio'] {
text-transform: none;
padding: 5px 10px;
white-space: nowrap;
border-radius: 4px;
border-radius: var(--border-radius);
background: var(--background-tooltip);
color: var(--text-color-tooltip);
text-align: left;

View File

@ -3,7 +3,7 @@
transition-property: background, border, box-shadow;
resize: vertical;
outline: 0;
border-radius: 4px;
border-radius: var(--border-radius);
border: 2px solid var(--input-border-color);
background: var(--input-background);
color: var(--input-color);

View File

@ -14,7 +14,7 @@
appearance: none;
border: 0;
text-decoration: none;
border-radius: 3px;
border-radius: var(--border-radius);
outline: none;
cursor: pointer;
background: none;

View File

@ -17,7 +17,7 @@ div.emoji-picker__wrapper {
}
.emoji-picker__variant-popup {
border-radius: 3px;
border-radius: var(--border-radius);
box-shadow: 0 3px 10px rgba(#3a3a3a, 0.1);
}

View File

@ -2,7 +2,7 @@
padding: 15px;
border-radius: 0 0 3px 3px;
background: var(--background-light);
border-radius: 3px;
border-radius: var(--border-radius);
border: 1px solid var(--background-light-highlight);
margin-top: -2px;
@ -42,7 +42,7 @@
.queryForm-filters-column {
display: flex;
flex-grow: 1;
gap: 10px;
gap: 6px;
}
.queryForm-filter {
@ -51,7 +51,7 @@
}
.queryForm-filter-label {
margin-right: 10px;
margin-right: 6px;
color: var(--color-grey);
font-size: 12px;
font-style: italic;

Some files were not shown because too many files have changed in this diff Show More