mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-07-10 16:01:58 +02:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b24900b9fb | |||
| 4c3e0a3810 | |||
| ce64fb7773 | |||
| 8369738c64 | |||
| 81734c1373 | |||
| 33bc77cc9d | |||
| 87822f6aa7 | |||
| 48612a5277 | |||
| e5941b4942 | |||
| 6e64dc4427 | |||
| 660cc406b5 | |||
| c374843e91 | |||
| 20dc5022e9 | |||
| 577cf00453 | |||
| d8b339a17c | |||
| cc78c1abb9 | |||
| e383cab402 | |||
| eb41ed0eb9 | |||
| f4f33b0e01 | |||
| 6679bb92fb | |||
| 67fe77a67c | |||
| 4a8d404905 | |||
| 804bb89b91 | |||
| a7985b971a | |||
| fda10ec67c | |||
| db520890a2 | |||
| 6266ba7a06 | |||
| bdc8e4b59d | |||
| 02a9295743 | |||
| 194b2b82cc | |||
| 85a51f8e2b | |||
| 34814e8723 | |||
| 95e2fe7b44 | |||
| 83473379db | |||
| 5af3cc4faf | |||
| 4a543396ae | |||
| 3ffa012857 | |||
| 00b6f918f3 | |||
| 7c1c1dd6ae | |||
| d7918fd82c | |||
| c062f6b6bd | |||
| 26e04e829c | |||
| 4d49e9c270 | |||
| daaae17113 | |||
| c38fbb034d | |||
| 0433908ae8 | |||
| 9e04922e7b | |||
| c6f2e3c7d1 |
+82
-7
@@ -9,6 +9,85 @@ toc_depth: 2
|
||||
|
||||
# :material-script-text: Changelog
|
||||
|
||||
## 9.6.0 <small>June 29th, 2026</small>
|
||||
|
||||
<p align="center">
|
||||
<img width="480" alt="aurora" src="https://github.com/user-attachments/assets/6103bb88-bc66-4222-9844-ed64d27dd2e7" />
|
||||
</p>
|
||||
|
||||
### Added
|
||||
|
||||
#### Customizable Fields
|
||||
|
||||
[Fields](fields.md) are now fully customizable!
|
||||
|
||||
- The built-in list of fields (now referred to as "field templates") has been reduced to a list of handy essential templates that can be fully modified or deleted
|
||||
- "Text Line" and "Text Box" field types have been combined into a single "Text" type with a "Multiline" option that can be configured on templates or toggled dynamically on existing text fields
|
||||
- You can now create your own field templates in with custom names, types, and other options
|
||||
- Fields applied to file entries can now have their names edited
|
||||
|
||||
<img width="314" height="507" alt="field_template_manager" src="https://github.com/user-attachments/assets/523fac64-e0d6-4697-ba25-6ff89273d0cb" />
|
||||
<img width="494" height="342" alt="text_field_editor" src="https://github.com/user-attachments/assets/918956ee-647b-43ca-9843-e8acb67ee58f" />
|
||||
|
||||
- refactor!: uncouple fields from hard-coded values by @CyanVoxel in #1354
|
||||
- fix: drop type_key column from text_fields and datetime_fields tables by @CyanVoxel in #1370
|
||||
- feat: field template manager by @TrigamDev in #1374
|
||||
- feat: add field template editor, editable field names by @CyanVoxel in #1396
|
||||
|
||||
#### Other Additions
|
||||
|
||||
- feat: add ability to configure automatic loading of new files over 10000 by @socalledtheraven in #1349
|
||||
- feat: dismiss update notification, change missing ffmpeg popup to banner by @CyanVoxel in #1400
|
||||
- feat: allow all raster images to be used as archive thumbnails. by @Sola-ris in #1373
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix: Fix update notification crash by @Sola-ris in #1278
|
||||
- fix: correct ESCAPABLE_CHARS in query_lang tokenizer by @hieuit095 in #1334
|
||||
- fix: empty ORLists now return false by @TheBobBobs in #1297
|
||||
- fix: update thumb grid when adding tags from preview panel by @TheBobBobs in #1245
|
||||
- fix: match against the correct path in the ignore registry by @TrigamDev in #1382
|
||||
- fix: fix tag aliases by @CyanVoxel in #1399
|
||||
- fix: fix faulty progress bars by @ludvig-sandh in #1293
|
||||
- perf: Optimize searching tags with DB indexes by @TheBobBobs in #1129
|
||||
|
||||
### Changed
|
||||
|
||||
- ui: add v9.6 assets, update misc resources by @CyanVoxel in #1398
|
||||
|
||||
#### Internal Changes
|
||||
|
||||
- minor refactor: cleanup parameters of open_library and open_sqlite_library by @Computerdores in #1294
|
||||
- refactor: sql migrations by @Computerdores in #1295
|
||||
- fix: remove preferences table by @Computerdores in #1298
|
||||
- doc: add REUSE license information by @Computerdores in #1361
|
||||
- docs/chore: add .editorconfig and .prettierrc.toml configs by @CyanVoxel in #1362
|
||||
- refactor(docs): uniform formatting pass by @CyanVoxel in #1363
|
||||
- fix: pyright errors in ts_qt.py by @Computerdores in #1237
|
||||
- fix(ci): address remaining pyright errors by @CyanVoxel in #1368
|
||||
- ci: add pyright workflow by @Computerdores in #1232
|
||||
- refactor(ci): remove mypy by @CyanVoxel in #1371
|
||||
- chore(deps-dev): bump pytest from 8.3.5 to 9.0.3 by @dependabot[bot] in #1372
|
||||
- doc(install): add common error message help text by @Computerdores in #1369
|
||||
- chore(ci): bump ruff to 0.15.17 by @CyanVoxel in #1393
|
||||
- fix(pyproject): allow Python 3.13 to function by @xarvex in #1047
|
||||
|
||||
#### Translations
|
||||
|
||||
- **Amharic** added by @Birhant _(11%, can manually enable in settings file)_
|
||||
- **Hebrew** added by @JonathanGlixman _(2%, can manually enable in settings file)_
|
||||
- **French** updated by @SodiumBismuth, @kitsumed
|
||||
- **Russian** updated by @WerDei, @NikitaNik-of, @Dott-rus
|
||||
- **Hungarian** updated by @smileyhead
|
||||
- **Japanese** updated by wany-oh
|
||||
- **Italian** updated by @EdelFlosWeiss
|
||||
- **Spanish** updated by @JulArr22
|
||||
- **Toki Pona** updated by Cyborus
|
||||
- **Viossa** updated by @Nginearing
|
||||
- **Portuguese** updated by ssantos
|
||||
|
||||
---
|
||||
|
||||
## 9.5.7 <small>May 5th, 2026</small>
|
||||
|
||||
This update adds several bugfixes and additions that have been sitting on the main branch for quite some time.
|
||||
@@ -68,12 +147,6 @@ This update adds several bugfixes and additions that have been sitting on the ma
|
||||
- **Tamil** updated by @TamilNeram
|
||||
- **Toki Pona** updated by @Math-Bee, Star Athendwyl
|
||||
|
||||
### New Contributors
|
||||
|
||||
- @Ambossmann made their first contribution in #1189
|
||||
- @CallMeHein made their first contribution in #1173
|
||||
- @terahidro2003 made their first contribution in #1328
|
||||
|
||||
---
|
||||
|
||||
## 9.5.6 <small>October 20th, 2025</small>
|
||||
@@ -422,7 +495,9 @@ A new "Library Information" window has been added and is accessible under the "V
|
||||
|
||||
## 9.5.0 <small>March 3rd, 2025</small>
|
||||
|
||||
<img width="500" src="https://github.com/user-attachments/assets/858f1494-216f-4521-aefe-d0aa4f754b9e" alt="TagStudio 9.5 Banner" />
|
||||
<p align="center">
|
||||
<img width="480" alt="TagStudio 9.5 Banner" src="https://github.com/user-attachments/assets/858f1494-216f-4521-aefe-d0aa4f754b9e" />
|
||||
</p>
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
+61
-43
@@ -18,9 +18,9 @@ Legacy (JSON) library save format versions were tied to the release version of t
|
||||
|
||||
### Versions 1.0.0 - 9.4.2
|
||||
|
||||
| Used From | Format | Location |
|
||||
| --------- | ------ | --------------------------------------------- |
|
||||
| v1.0.0 | JSON | `<Library Folder>`/.TagStudio/ts_library.json |
|
||||
| Used in Releases | Format | Location |
|
||||
| ---------------- | ------ | --------------------------------------------- |
|
||||
| v1.0.0 - v9.4.2 | JSON | `<Library Folder>`/.TagStudio/ts_library.json |
|
||||
|
||||
The legacy database format for public TagStudio releases [v9.1](https://github.com/TagStudioDev/TagStudio/tree/Alpha-v9.1) through [v9.4.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.4.2). Variations of this format had been used privately since v1.0.0.
|
||||
|
||||
@@ -30,7 +30,11 @@ Replaced by the new SQLite format introduced in TagStudio [v9.5.0 Pre-Release 1]
|
||||
|
||||
## SQLite <small>v9.5.0+</small>
|
||||
|
||||
Starting with TagStudio [v9.5.0-pr1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1), the library save format has been moved to a [SQLite](https://sqlite.org) format. Legacy JSON libraries are migrated (with the user's consent) to the new format when opening in current versions of the program. The save format versioning is now separate from the program's versioning number.
|
||||
Starting with TagStudio [v9.5.0-pr1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1), the library save format has been moved to the [SQLite](https://sqlite.org) format. Legacy JSON libraries are migrated (with the user's consent) to the new format when opening in current versions of the program. The save format versioning is now separate from the program's versioning number.
|
||||
|
||||
The database storage location of all SQLite versions to date is at:
|
||||
|
||||
`<Library Folder>`/.TagStudio/ts_library.sqlite
|
||||
|
||||
### Versioning
|
||||
|
||||
@@ -38,12 +42,12 @@ Versions **1-100** stored the database version in a table called `preferences` i
|
||||
|
||||
Versions **>101** store the database version in a table called `versions` in a row with the `key` column of `'CURRENT'` inside the corresponding `value` column. The `versions` table also stores the initial database version in which the file was created with under the `'INITIAL'` key. Databases created before this key was introduced will always have `'INITIAL'` value of `100`.
|
||||
|
||||
#### "versions" Table
|
||||
#### `versions` Table
|
||||
|
||||
| key (`VARCHAR`) | value (`INTEGER`) |
|
||||
| --------------- | --------------------------------------------- |
|
||||
| `'INITIAL'` | <Version DB was created with, minimum `100`\> |
|
||||
| `'CURRENT'` | <Current version of DB\> |
|
||||
| key (`VARCHAR`) | value (`INTEGER`) |
|
||||
| --------------- | ------------------------------------------ |
|
||||
| `'INITIAL'` | Version DB was created with, minimum `100` |
|
||||
| `'CURRENT'` | Current version of DB |
|
||||
|
||||
#### Major and Minor Versioning
|
||||
|
||||
@@ -51,19 +55,23 @@ Version **100** came along with a major/minor versioning system built into to th
|
||||
|
||||
For example, a database with version 204 would still be readable in an older version of TagStudio that understands version 200. A database with version 300, on the other hand, would no longer be readable in that same older version and an error message would display.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! note ""Version 0" Message"
|
||||
If you see an message when opening a library along the lines of **"Found Version 0"**, this means that you're opening a library created with a newer version of TagStudio in an older version of TagStudio that does not recognize the future versioning system. To open your library, you should use a TagStudio version greater than or equal to the one the library was created or last used with.
|
||||
|
||||
---
|
||||
|
||||
### Versions 1 - 5
|
||||
|
||||
These versions were used while developing the new SQLite file format, outside any official or recommended release. These versions **were never supported** in any official capacity and were actively warned against using for real libraries.
|
||||
These versions were used while developing the new SQLite file format, outside of any official or recommended release. These versions **were never supported** in any official capacity and were actively warned against using for real libraries.
|
||||
|
||||
---
|
||||
|
||||
### Version 6
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [v9.5.0-pr1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------------------- | ------ |
|
||||
| d1b006a8978a7fa1b7f1a82243e490aca8a8355e | [v9.5.0-pr1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1) | SQLite |
|
||||
|
||||
The first public version of the SQLite save file format.
|
||||
|
||||
@@ -73,9 +81,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
### Version 7
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [v9.5.0-pr2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr2) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------------------- | ------ |
|
||||
| 480328b83bc1c69ab52a3eb11e2935337d3460ab | [v9.5.0-pr2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr2) | SQLite |
|
||||
|
||||
- ~~Repairs "Description" fields to use a TEXT_LINE key instead of a TEXT_BOX key.~~ _See [Version 200](#version-200)_
|
||||
- Repairs tags that may have a disambiguation_id pointing towards a deleted tag.
|
||||
@@ -84,9 +92,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
### Version 8
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [v9.5.0-pr4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr4) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------------------- | ------ |
|
||||
| 28de21ade757aa5b80e87f77a459f4a3af21ffe0 | [v9.5.0-pr4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr4) | SQLite |
|
||||
|
||||
- Adds the `color_border` column to the `tag_colors` table. Used for instructing the [secondary color](colors.md#secondary-color) to apply to a tag's border as a new optional behavior.
|
||||
- Adds three new default colors: "Burgundy (TagStudio Shades)", "Dark Teal (TagStudio Shades)", and "Dark Lavender (TagStudio Shades)".
|
||||
@@ -96,9 +104,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
### Version 9
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ----------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [v9.5.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.2) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | ----------------------------------------------------------------------- | ------ |
|
||||
| 31833245a4d7a655dc4e66e6005a9dd78c36c04d | [v9.5.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.2) | SQLite |
|
||||
|
||||
- Adds the `filename` column to the `entries` table. Used for sorting entries by filename in search results.
|
||||
|
||||
@@ -108,9 +116,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
#### Version 100
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 74383e3c3c12f72be1481ab0b86c7360b95c2d85 | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | --------------------- | ------ |
|
||||
| 74383e3c3c12f72be1481ab0b86c7360b95c2d85 | _None_ | SQLite |
|
||||
|
||||
- Introduces built-in minor versioning
|
||||
- The version number divided by 100 (and floored) constitutes the **major** version. Major version indicate breaking changes that prevent libraries from being opened in TagStudio versions older than the ones they were created in.
|
||||
@@ -119,9 +127,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
#### Version 101
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 12e074b71d8860282b44e49e0e1a41b7a2e4bae8/[v9.5.4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.4) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | ----------------------------------------------------------------------- | ------ |
|
||||
| 12e074b71d8860282b44e49e0e1a41b7a2e4bae8 | [v9.5.4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.4) | SQLite |
|
||||
|
||||
- Deprecates the `preferences` table, set to be removed in a [future](#version-104) TagStudio version.
|
||||
- Introduces the `versions` table
|
||||
@@ -133,24 +141,26 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
#### Version 102
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 71d04254cf87f4200bb7ffc81656e50dfb122e4d | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | ----------------------------------------------------------------------- | ------ |
|
||||
| 71d04254cf87f4200bb7ffc81656e50dfb122e4d | [v9.5.5](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.5) | SQLite |
|
||||
|
||||
- Applies repairs to the `tag_parents` table created in [version 100](#version-100), removing rows that reference tags that have been deleted.
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 88d0b47a86821ccfadba653f30a515abce5b24b0/[v9.5.7](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.7) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
#### Version 103
|
||||
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | ----------------------------------------------------------------------- | ------ |
|
||||
| 88d0b47a86821ccfadba653f30a515abce5b24b0 | [v9.5.7](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.7) | SQLite |
|
||||
|
||||
- Adds the `is_hidden` column to the `tags` table (default `0`). Used for excluding entries tagged with hidden tags from library searches.
|
||||
- Sets the `is_hidden` field on the built-in Archived tag to `1`, to match the Archived tag now being hidden by default.
|
||||
|
||||
#### Version 104
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| ad2cbbca483018d245b44348e2c4f5a0e0bb28f1 | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | --------------------- | ------ |
|
||||
| ad2cbbca483018d245b44348e2c4f5a0e0bb28f1 | _None_ | SQLite |
|
||||
|
||||
- Removes the `preferences` table, after migrating the contained extension list to the .ts_ignore file, if necessary.
|
||||
|
||||
@@ -160,9 +170,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
#### Version 200
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| c15e2b56eedd0a3c13391fa43571b8f8f7c7a91f | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | --------------------- | ------ |
|
||||
| c15e2b56eedd0a3c13391fa43571b8f8f7c7a91f | _None_ | SQLite |
|
||||
|
||||
- Adds `text_field_templates` and `date_field_templates` tables.
|
||||
- Drops `boolean_fields` and `value_type` tables.
|
||||
@@ -177,9 +187,17 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
#### Version 201
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 38da7bb3a920a01d4d70fa065fd19c83ff6eecb1 | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| ---------------------------------------- | ----------------------------------------------------------------------- | ------ |
|
||||
| 38da7bb3a920a01d4d70fa065fd19c83ff6eecb1 | [v9.6.0](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.6.0) | SQLite |
|
||||
|
||||
- Drops `type_key` columns from `text_fields` and `datetime_fields` tables.
|
||||
- Enforces column positions for `text_fields` and `datetime_fields` tables.
|
||||
|
||||
#### Version 202
|
||||
|
||||
| Added in Commit | Introduced in Release | Format |
|
||||
| --------------- | ----------------------------------------------------------------------- | ------ |
|
||||
| | [v9.6.1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.6.1) | SQLite |
|
||||
|
||||
- Applies repairs to the `tag_parents` table, removing rows that reference child tags that have been deleted.
|
||||
|
||||
+5
-6
@@ -122,7 +122,6 @@ A detailed specification written for the TagStudio tag and/or library format. In
|
||||
- [ ] Tag Search
|
||||
- [ ] Pinned Tags
|
||||
- [ ] New Tabbed Tag Building UI to Support New Tag Features :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.8.x]**
|
||||
- [ ] Custom Thumbnail Overrides :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
|
||||
- [ ] Media Duration Labels :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
|
||||
- [ ] Word/Line Count Labels :material-chevron-up:{ .priority-low title="Low Priority" }
|
||||
- [ ] Custom Tag Badges :material-chevron-up:{ .priority-low title="Low Priority" }
|
||||
@@ -174,14 +173,14 @@ File or file-like [entries](entries.md) stored in the library.
|
||||
- [x] Datetimes **[[v9.5.4](changelog.md#954-september-1st-2025)]**
|
||||
- [ ] Numeric Fields :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.6.x]**
|
||||
- [ ] Optional Units (e.g. inches, cm, height notation, degrees, bytes, etc.) :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Custom Field Names :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
|
||||
- [x] Removal of Deprecated Fields **[v9.6.0]**
|
||||
- [x] Custom Field Names **[[v9.6.0](changelog.md#960-june-29th-2026)]**
|
||||
- [x] Removal of Deprecated Fields **[[v9.6.0](changelog.md#960-june-29th-2026)]**
|
||||
- [ ] Custom Thumbnail Overrides :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
|
||||
- [ ] Entry Groups :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
|
||||
- [ ] Non-exclusive; Entries can be in multiple groups :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Ability to number entries within group :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Ability to number entries within group (i.e. page numbers) :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Ability to set sorting method for group :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Ability to set custom thumbnail for group :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Group is treated as entry with tags and metadata :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Ability to set custom thumbnail for group :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Nested groups :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
|
||||
### :material-tag-text: Tags
|
||||
|
||||
Generated
+6
-6
@@ -7,11 +7,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763759067,
|
||||
"narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=",
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -22,11 +22,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1763835633,
|
||||
"narHash": "sha256-HzxeGVID5MChuCPESuC0dlQL1/scDKu+MmzoVBJxulM=",
|
||||
"lastModified": 1782723713,
|
||||
"narHash": "sha256-oPXCU/SSUokcGaJREHibG1CBX3+s/W7orDWQOZDsEeQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "050e09e091117c3d7328c7b2b7b577492c43c134",
|
||||
"rev": "b5aa0fbd538984f6e3d201be0005b4463d8b09f8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
{
|
||||
ffmpeg-headless,
|
||||
lib,
|
||||
@@ -84,8 +83,10 @@ python3Packages.buildPythonApplication {
|
||||
"py7zr"
|
||||
"pyside6"
|
||||
"rarfile"
|
||||
"rawpy"
|
||||
"requests"
|
||||
"semver"
|
||||
"Send2Trash"
|
||||
"structlog"
|
||||
"typing-extensions"
|
||||
];
|
||||
@@ -130,6 +131,7 @@ python3Packages.buildPythonApplication {
|
||||
"test_close_library" # TODO: Look into segfault.
|
||||
"test_flow_layout_happy_path"
|
||||
"test_get" # TODO: Look further into, might be possible to run.
|
||||
"test_github_api_unavailable"
|
||||
"test_json_migration"
|
||||
"test_library_migrations"
|
||||
"test_update_tags"
|
||||
|
||||
+1
-1
@@ -1,7 +1,6 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
@@ -78,6 +77,7 @@ pkgs.mkShellNoCC {
|
||||
coreutils
|
||||
uv
|
||||
|
||||
pyright
|
||||
ruff
|
||||
];
|
||||
buildInputs = [
|
||||
|
||||
+7
-3
@@ -30,7 +30,7 @@ dependencies = [
|
||||
"PySide6==6.8.0.*",
|
||||
"rarfile==4.2",
|
||||
"rawpy~=0.27",
|
||||
"Send2Trash~=1.8",
|
||||
"Send2Trash>=1.8,<3",
|
||||
"SQLAlchemy~=2.0",
|
||||
"srctools~=2.6",
|
||||
"structlog~=25.3",
|
||||
@@ -65,9 +65,12 @@ packages = ["src/tagstudio"]
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
#addopts = "-m 'not qt'"
|
||||
qt_api = "pyside6"
|
||||
pythonpath = ["src"]
|
||||
filterwarnings = [
|
||||
"ignore:DISTINCT ON is currently supported only by the PostgreSQL dialect:sqlalchemy.exc.SADeprecationWarning",
|
||||
'ignore:Invoking or_\(\) without arguments is deprecated:sqlalchemy.exc.SADeprecationWarning',
|
||||
]
|
||||
|
||||
[tool.pyright]
|
||||
ignore = [
|
||||
@@ -76,7 +79,8 @@ ignore = [
|
||||
"src/tagstudio/qt/previews/vendored/pydub/",
|
||||
]
|
||||
include = ["src/tagstudio", "tests"]
|
||||
# reference for the settings here: https://github.com/microsoft/pyright/blob/main/docs/configuration.md
|
||||
# Reference for the settings here: https://github.com/microsoft/pyright/blob/main/docs/configuration.md
|
||||
# Some rules exclusive to basedpyright: https://docs.basedpyright.com/latest/benefits-over-pyright/new-diagnostic-rules/
|
||||
reportAny = false
|
||||
reportIgnoreCommentWithoutRule = false
|
||||
reportImplicitStringConcatenation = false
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from tagstudio.main import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -9,7 +9,7 @@ JSON_FILENAME: str = "ts_library.json"
|
||||
|
||||
DB_VERSION_CURRENT_KEY: str = "CURRENT"
|
||||
DB_VERSION_INITIAL_KEY: str = "INITIAL"
|
||||
DB_VERSION: int = 201
|
||||
DB_VERSION: int = 202
|
||||
|
||||
TAG_CHILDREN_QUERY = text("""
|
||||
WITH RECURSIVE ChildTags AS (
|
||||
|
||||
@@ -42,13 +42,17 @@ def make_engine(connection_string: str) -> Engine:
|
||||
|
||||
def make_tables(engine: Engine) -> None:
|
||||
logger.info("[Library] Creating DB tables...")
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
# tag IDs < 1000 are reserved
|
||||
# create tag and delete it to bump the autoincrement sequence
|
||||
# TODO - find a better way
|
||||
# is this the better way?
|
||||
with engine.connect() as conn:
|
||||
# TODO: this should instead be migrations that create the exact tables that were added in
|
||||
# the respective DB versions
|
||||
Base.metadata.create_all(conn)
|
||||
conn.commit()
|
||||
|
||||
# TODO: this needs to be a migration
|
||||
# tag IDs < 1000 are reserved
|
||||
# create tag and delete it to bump the autoincrement sequence
|
||||
# TODO - find a better way
|
||||
# is this the better way?
|
||||
result = conn.execute(text("SELECT SEQ FROM sqlite_sequence WHERE name='tags'"))
|
||||
autoincrement_val = result.scalar()
|
||||
if not autoincrement_val or autoincrement_val <= RESERVED_TAG_END:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.controllers.field_template_search_panel_controller import FieldTemplateSearchModal
|
||||
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal
|
||||
from tagstudio.qt.previews.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.views.preview_panel_view import PreviewPanelView
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
@@ -22,7 +23,10 @@ class PreviewPanel(PreviewPanelView):
|
||||
super().__init__(library, driver)
|
||||
|
||||
self.__add_field_modal = FieldTemplateSearchModal(self.lib, is_field_template_chooser=True)
|
||||
self.__add_tag_modal = TagSearchModal(self.lib, is_tag_chooser=True)
|
||||
self.__add_tag_modal = TagSearchModal(
|
||||
self.lib, title=Translations["tag.add.plural"], is_tag_chooser=True
|
||||
)
|
||||
self.__add_tag_modal.tsp.set_driver(driver)
|
||||
self._thumb.check_ffmpeg.connect(self._toggle_ffmpeg_warning)
|
||||
|
||||
@typing.override
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
from typing import override
|
||||
from warnings import catch_warnings
|
||||
|
||||
import structlog
|
||||
@@ -20,10 +20,6 @@ from tagstudio.qt.views.tag_search_panel_view import TagSearchPanelView
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class TagSearchModal(PanelModal):
|
||||
tsp: "TagSearchPanel"
|
||||
@@ -31,6 +27,7 @@ class TagSearchModal(PanelModal):
|
||||
def __init__(
|
||||
self,
|
||||
library: Library,
|
||||
title: str,
|
||||
exclude: list[int] | None = None,
|
||||
is_tag_chooser: bool = True,
|
||||
has_save: bool = False,
|
||||
@@ -42,8 +39,8 @@ class TagSearchModal(PanelModal):
|
||||
view=TagSearchPanelView(is_tag_chooser),
|
||||
)
|
||||
super().__init__(
|
||||
self.tsp,
|
||||
Translations["tag.add.plural"],
|
||||
widget=self.tsp,
|
||||
title=title,
|
||||
is_savable=has_save,
|
||||
)
|
||||
|
||||
@@ -169,17 +166,25 @@ class TagSearchPanel(SearchPanel[Tag]):
|
||||
# Connect callbacks
|
||||
tag_widget.on_edit.connect(lambda edit_tag=item: self.on_item_edit(edit_tag))
|
||||
tag_widget.on_remove.connect(lambda remove_tag=item: self._on_item_remove(remove_tag))
|
||||
tag_widget.bg_button.clicked.connect(
|
||||
lambda checked=False, tag=item: self._on_item_chosen(tag)
|
||||
)
|
||||
if self.is_chooser:
|
||||
tag_widget.bg_button.clicked.connect(
|
||||
lambda checked=False, tag=item: self._on_item_chosen(tag)
|
||||
)
|
||||
else:
|
||||
tag_widget.bg_button.clicked.connect(
|
||||
lambda checked=False, edit_tag=item: self.on_item_edit(edit_tag)
|
||||
)
|
||||
|
||||
# Connect search action
|
||||
if self._driver is not None:
|
||||
tag_widget.search_for_tag_action.triggered.connect(
|
||||
lambda tag_id=item.id: self.search_for_tag(tag_id)
|
||||
lambda checked=False, tag_id=item.id: self.search_for_tag(tag_id)
|
||||
)
|
||||
tag_widget.search_for_tag_action.setEnabled(True)
|
||||
else:
|
||||
logger.warning(
|
||||
"[TagSearchPanel] No driver was set for this TagSearchPanel. Was this on purpose?"
|
||||
)
|
||||
tag_widget.search_for_tag_action.setEnabled(False)
|
||||
|
||||
@override
|
||||
|
||||
@@ -176,7 +176,9 @@ class BuildTagPanel(PanelWidget):
|
||||
if tag is not None:
|
||||
exclude_ids.append(tag.id)
|
||||
|
||||
self.add_tag_modal = TagSearchModal(self.lib, exclude_ids)
|
||||
self.add_tag_modal = TagSearchModal(
|
||||
self.lib, title=Translations["tag.add.plural"], exclude=exclude_ids
|
||||
)
|
||||
self.add_tag_modal.tsp.item_chosen.connect(lambda x: self._add_parent_tag_callback(x))
|
||||
self.parent_tags_add_button.clicked.connect(self.add_tag_modal.show)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from PySide6.QtWidgets import (
|
||||
QLabel,
|
||||
QMessageBox,
|
||||
QProgressDialog,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
@@ -47,7 +48,6 @@ from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.utils.custom_runnable import CustomRunnable
|
||||
from tagstudio.qt.utils.function_iterator import FunctionIterator
|
||||
from tagstudio.qt.views.paged_body_wrapper import PagedBodyWrapper
|
||||
from tagstudio.qt.views.qbutton_wrapper import QPushButtonWrapper
|
||||
from tagstudio.qt.views.stylesheets.stylesheets import header
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
@@ -99,8 +99,8 @@ class JsonMigrationModal(QObject):
|
||||
body_wrapper.layout().addWidget(body_label)
|
||||
body_wrapper.layout().setContentsMargins(0, 36, 0, 0)
|
||||
|
||||
cancel_button = QPushButtonWrapper(Translations["generic.cancel"])
|
||||
next_button = QPushButtonWrapper(Translations["generic.continue"])
|
||||
cancel_button = QPushButton(Translations["generic.cancel"])
|
||||
next_button = QPushButton(Translations["generic.continue"])
|
||||
cancel_button.clicked.connect(self.migration_cancelled.emit)
|
||||
|
||||
self.stack.append(
|
||||
@@ -289,14 +289,12 @@ class JsonMigrationModal(QObject):
|
||||
self.body_wrapper_01.layout().addWidget(desc_label)
|
||||
self.body_wrapper_01.layout().setSpacing(12)
|
||||
|
||||
back_button = QPushButtonWrapper(Translations["generic.navigation.back"])
|
||||
start_button = QPushButtonWrapper(Translations["json_migration.start_and_preview"])
|
||||
back_button = QPushButton(Translations["generic.navigation.back"])
|
||||
start_button = QPushButton(Translations["json_migration.start_and_preview"])
|
||||
start_button.setMinimumWidth(120)
|
||||
start_button.clicked.connect(self.migrate)
|
||||
start_button.clicked.connect(lambda: start_button.setDisabled(True))
|
||||
finish_button: QPushButtonWrapper = QPushButtonWrapper(
|
||||
Translations["json_migration.finish_migration"]
|
||||
)
|
||||
finish_button = QPushButton(Translations["json_migration.finish_migration"])
|
||||
finish_button.setMinimumWidth(120)
|
||||
finish_button.setDisabled(True)
|
||||
finish_button.clicked.connect(self.finish_migration)
|
||||
@@ -381,7 +379,7 @@ class JsonMigrationModal(QObject):
|
||||
pb.setMinimum(1),
|
||||
pb.setValue(1),
|
||||
# Enable the finish button
|
||||
cast(QPushButtonWrapper, self.stack[1].buttons[4]).setDisabled(False),
|
||||
cast(QPushButton, self.stack[1].buttons[4]).setDisabled(False),
|
||||
)
|
||||
)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
@@ -397,14 +395,15 @@ class JsonMigrationModal(QObject):
|
||||
# Convert JSON Library to SQLite
|
||||
yield Translations["json_migration.creating_database_tables"]
|
||||
self.sql_lib = SqliteLibrary()
|
||||
self.temp_path: Path = (
|
||||
self.json_lib.library_dir / TS_FOLDER_NAME / "migration_ts_library.sqlite"
|
||||
)
|
||||
temp_filename = "migration_ts_library.sqlite"
|
||||
self.temp_path: Path = self.json_lib.library_dir / TS_FOLDER_NAME / temp_filename
|
||||
if self.temp_path.exists():
|
||||
logger.info('Temporary migration file "temp_path" already exists. Removing...')
|
||||
self.temp_path.unlink()
|
||||
self.sql_lib.open_sqlite_library(
|
||||
self.json_lib.library_dir, is_new=True, storage_path=str(self.temp_path)
|
||||
self.sql_lib.create_sqlite_library(
|
||||
self.json_lib.library_dir,
|
||||
in_memory=False,
|
||||
sql_filename=temp_filename,
|
||||
)
|
||||
yield Translations.format(
|
||||
"json_migration.migrating_files_entries", entries=len(self.json_lib.entries)
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
"""A pagination widget created for TagStudio."""
|
||||
|
||||
from typing import cast, override
|
||||
from warnings import catch_warnings
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import QSize, Signal
|
||||
from PySide6.QtGui import QIntValidator, QPixmap
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSizePolicy, QWidget
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QPushButton, QSizePolicy, QWidget
|
||||
|
||||
from tagstudio.qt.helpers.color_overlay import auto_theme_overlay
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.views.qbutton_wrapper import QPushButtonWrapper
|
||||
|
||||
|
||||
class Pagination(QWidget):
|
||||
@@ -21,8 +21,8 @@ class Pagination(QWidget):
|
||||
|
||||
index = Signal(int)
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.rm = ResourceManager()
|
||||
self.page_count: int = 0
|
||||
self.current_page_index: int = 0
|
||||
@@ -43,7 +43,7 @@ class Pagination(QWidget):
|
||||
self.root_layout.setSpacing(3)
|
||||
|
||||
# [<] ----------------------------------
|
||||
self.prev_button = QPushButtonWrapper()
|
||||
self.prev_button = QPushButton()
|
||||
prev_icon: Image.Image = self.rm.bxs_left_arrow
|
||||
prev_icon = auto_theme_overlay(prev_icon, use_alpha=False)
|
||||
self.prev_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(prev_icon)))
|
||||
@@ -52,7 +52,7 @@ class Pagination(QWidget):
|
||||
self.prev_button.setMaximumSize(self.button_size)
|
||||
|
||||
# --- [1] ------------------------------
|
||||
self.start_button = QPushButtonWrapper()
|
||||
self.start_button = QPushButton()
|
||||
self.start_button.setMinimumSize(self.button_size)
|
||||
self.start_button.setMaximumSize(self.button_size)
|
||||
|
||||
@@ -91,12 +91,12 @@ class Pagination(QWidget):
|
||||
self.end_ellipses.setText(". . .")
|
||||
|
||||
# ----------------------------- [42] ---
|
||||
self.end_button = QPushButtonWrapper()
|
||||
self.end_button = QPushButton()
|
||||
self.end_button.setMinimumSize(self.button_size)
|
||||
self.end_button.setMaximumSize(self.button_size)
|
||||
|
||||
# ---------------------------------- [>]
|
||||
self.next_button = QPushButtonWrapper()
|
||||
self.next_button = QPushButton()
|
||||
next_icon: Image.Image = self.rm.bxs_right_arrow
|
||||
next_icon = auto_theme_overlay(next_icon, use_alpha=False)
|
||||
self.next_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(next_icon)))
|
||||
@@ -223,7 +223,7 @@ class Pagination(QWidget):
|
||||
)
|
||||
self._assign_click(
|
||||
cast(
|
||||
QPushButtonWrapper,
|
||||
QPushButton,
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget(),
|
||||
),
|
||||
i,
|
||||
@@ -240,7 +240,7 @@ class Pagination(QWidget):
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setText(str(i + 1)) # pyright: ignore[reportAttributeAccessIssue]
|
||||
self._assign_click(
|
||||
cast(
|
||||
QPushButtonWrapper,
|
||||
QPushButton,
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget(),
|
||||
),
|
||||
i,
|
||||
@@ -268,21 +268,20 @@ class Pagination(QWidget):
|
||||
def _goto_page(self, index: int):
|
||||
self.update_buttons(self.page_count, index)
|
||||
|
||||
def _assign_click(self, button: QPushButtonWrapper, index):
|
||||
if button.is_connected:
|
||||
def _assign_click(self, button: QPushButton, index: int):
|
||||
with catch_warnings(record=True):
|
||||
button.clicked.disconnect()
|
||||
button.clicked.connect(lambda checked=False, i=index: self._goto_page(i))
|
||||
button.is_connected = True
|
||||
|
||||
def _populate_buffer_buttons(self):
|
||||
for _ in range(max(self.buffer_page_count * 2, 5)):
|
||||
button = QPushButtonWrapper()
|
||||
button = QPushButton()
|
||||
button.setMinimumSize(self.button_size)
|
||||
button.setMaximumSize(self.button_size)
|
||||
button.setHidden(True)
|
||||
self.start_buffer_layout.addWidget(button)
|
||||
|
||||
end_button = QPushButtonWrapper()
|
||||
end_button = QPushButton()
|
||||
end_button.setMinimumSize(self.button_size)
|
||||
end_button.setMaximumSize(self.button_size)
|
||||
end_button.setHidden(True)
|
||||
|
||||
@@ -98,6 +98,9 @@ class SettingsPanel(PanelWidget):
|
||||
self.root_layout.setContentsMargins(0, 6, 0, 0)
|
||||
|
||||
self.library_settings_container = QWidget()
|
||||
self.appearance_settings_container = QWidget()
|
||||
self.localization_settings_container = QWidget()
|
||||
self.media_settings_container = QWidget()
|
||||
|
||||
# Tabs
|
||||
self.tab_widget = QTabWidget()
|
||||
@@ -105,6 +108,19 @@ class SettingsPanel(PanelWidget):
|
||||
self.__build_global_settings()
|
||||
self.tab_widget.addTab(self.global_settings_container, Translations["settings.global"])
|
||||
|
||||
self.__build_appearance_settings()
|
||||
self.tab_widget.addTab(
|
||||
self.appearance_settings_container, Translations["settings.appearance"]
|
||||
)
|
||||
|
||||
self.__build_localization_settings()
|
||||
self.tab_widget.addTab(
|
||||
self.localization_settings_container, Translations["settings.localization"]
|
||||
)
|
||||
|
||||
self.__build_media_settings()
|
||||
self.tab_widget.addTab(self.media_settings_container, Translations["settings.media"])
|
||||
|
||||
# self.__build_library_settings()
|
||||
# self.tab_widget.addTab(self.library_settings_container, Translations["settings.library"])
|
||||
|
||||
@@ -132,17 +148,6 @@ class SettingsPanel(PanelWidget):
|
||||
form_layout = QFormLayout(self.global_settings_container)
|
||||
form_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
# Language
|
||||
self.language_combobox = QComboBox()
|
||||
for k in LANGUAGES:
|
||||
self.language_combobox.addItem(k, LANGUAGES[k])
|
||||
current_lang: str = self.driver.settings.language
|
||||
if current_lang not in LANGUAGES.values():
|
||||
current_lang = DEFAULT_TRANSLATION
|
||||
self.language_combobox.setCurrentIndex(list(LANGUAGES.values()).index(current_lang))
|
||||
self.language_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.language"], self.language_combobox)
|
||||
|
||||
# Open Last Library on Start
|
||||
self.open_last_lib_checkbox = QCheckBox()
|
||||
self.open_last_lib_checkbox.setChecked(self.driver.settings.open_last_loaded_on_startup)
|
||||
@@ -150,54 +155,6 @@ class SettingsPanel(PanelWidget):
|
||||
Translations["settings.open_library_on_start"], self.open_last_lib_checkbox
|
||||
)
|
||||
|
||||
# Generate Thumbnails
|
||||
self.generate_thumbs = QCheckBox()
|
||||
self.generate_thumbs.setChecked(self.driver.settings.generate_thumbs)
|
||||
form_layout.addRow(Translations["settings.generate_thumbs"], self.generate_thumbs)
|
||||
|
||||
# Thumbnail Cache Size
|
||||
self.thumb_cache_size_container = QWidget()
|
||||
self.thumb_cache_size_layout = QHBoxLayout(self.thumb_cache_size_container)
|
||||
self.thumb_cache_size_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.thumb_cache_size_layout.setSpacing(6)
|
||||
self.thumb_cache_size = QLineEdit()
|
||||
self.thumb_cache_size.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
self.thumb_cache_size_validator = QDoubleValidator(
|
||||
MIN_THUMB_CACHE_SIZE, 1_000_000_000, 2
|
||||
) # High limit
|
||||
self.thumb_cache_size.setValidator(self.thumb_cache_size_validator)
|
||||
self.thumb_cache_size.setText(
|
||||
str(max(self.driver.settings.thumb_cache_size, MIN_THUMB_CACHE_SIZE)).removesuffix(".0")
|
||||
)
|
||||
self.thumb_cache_size_layout.addWidget(self.thumb_cache_size)
|
||||
self.thumb_cache_size_layout.setStretch(1, 2)
|
||||
self.thumb_cache_size_layout.addWidget(QLabel("MiB"))
|
||||
form_layout.addRow(
|
||||
Translations["settings.thumb_cache_size.label"], self.thumb_cache_size_container
|
||||
)
|
||||
|
||||
# Cached Thumbnail Resolution
|
||||
self.cached_thumb_res_container = QWidget()
|
||||
self.cached_thumb_res_layout = QHBoxLayout(self.cached_thumb_res_container)
|
||||
self.cached_thumb_res_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.cached_thumb_res_layout.setSpacing(6)
|
||||
self.cached_thumb_res = QLineEdit()
|
||||
self.cached_thumb_res.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
self.cached_thumb_res_validator = QIntValidator(MIN_CACHED_THUMB_RES, MAX_CACHED_THUMB_RES)
|
||||
self.cached_thumb_res.setValidator(self.cached_thumb_res_validator)
|
||||
self.cached_thumb_res.setText(str(self.driver.settings.cached_thumb_resolution))
|
||||
self.cached_thumb_res_layout.addWidget(self.cached_thumb_res)
|
||||
self.cached_thumb_res_layout.setStretch(1, 2)
|
||||
self.cached_thumb_res_layout.addWidget(QLabel("px"))
|
||||
form_layout.addRow(
|
||||
Translations["settings.cached_thumb_resolution.label"], self.cached_thumb_res_container
|
||||
)
|
||||
|
||||
# Autoplay
|
||||
self.autoplay_checkbox = QCheckBox()
|
||||
self.autoplay_checkbox.setChecked(self.driver.settings.autoplay)
|
||||
form_layout.addRow(Translations["media_player.autoplay"], self.autoplay_checkbox)
|
||||
|
||||
# Scan for new files when a library is opened
|
||||
self.scan_files_on_open_checkbox = QCheckBox()
|
||||
self.scan_files_on_open_checkbox.setChecked(self.driver.settings.scan_files_on_open)
|
||||
@@ -259,26 +216,83 @@ class SettingsPanel(PanelWidget):
|
||||
Translations["settings.tag_click_action.label"], self.tag_click_action_combobox
|
||||
)
|
||||
|
||||
# Dark Mode
|
||||
self.theme_combobox = QComboBox()
|
||||
for k in SettingsPanel.theme_map:
|
||||
self.theme_combobox.addItem(SettingsPanel.theme_map[k], k)
|
||||
theme = self.driver.settings.theme
|
||||
if theme not in SettingsPanel.theme_map:
|
||||
theme = Theme.DEFAULT
|
||||
self.theme_combobox.setCurrentIndex(list(SettingsPanel.theme_map.keys()).index(theme))
|
||||
self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox)
|
||||
# TODO: Implement Library Settings
|
||||
def __build_library_settings(self): # pyright: ignore[reportUnusedFunction]
|
||||
form_layout = QFormLayout(self.library_settings_container)
|
||||
form_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
# Splash Screen
|
||||
self.splash_combobox = QComboBox()
|
||||
for k in SettingsPanel.splash_map:
|
||||
self.splash_combobox.addItem(SettingsPanel.splash_map[k], k)
|
||||
splash = self.driver.settings.splash
|
||||
if splash not in SettingsPanel.splash_map:
|
||||
splash = Splash.DEFAULT
|
||||
self.splash_combobox.setCurrentIndex(list(SettingsPanel.splash_map.keys()).index(splash))
|
||||
form_layout.addRow(Translations["settings.splash.label"], self.splash_combobox)
|
||||
todo_label = QLabel("TODO")
|
||||
form_layout.addRow(todo_label)
|
||||
|
||||
def __build_media_settings(self):
|
||||
form_layout = QFormLayout(self.media_settings_container)
|
||||
form_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
# Autoplay
|
||||
self.autoplay_checkbox = QCheckBox()
|
||||
self.autoplay_checkbox.setChecked(self.driver.settings.autoplay)
|
||||
form_layout.addRow(Translations["media_player.autoplay"], self.autoplay_checkbox)
|
||||
|
||||
# Generate Thumbnails
|
||||
self.generate_thumbs = QCheckBox()
|
||||
self.generate_thumbs.setChecked(self.driver.settings.generate_thumbs)
|
||||
form_layout.addRow(Translations["settings.generate_thumbs"], self.generate_thumbs)
|
||||
|
||||
# Thumbnail Cache Size
|
||||
self.thumb_cache_size_container = QWidget()
|
||||
self.thumb_cache_size_layout = QHBoxLayout(self.thumb_cache_size_container)
|
||||
self.thumb_cache_size_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.thumb_cache_size_layout.setSpacing(6)
|
||||
self.thumb_cache_size = QLineEdit()
|
||||
self.thumb_cache_size.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
self.thumb_cache_size_validator = QDoubleValidator(
|
||||
MIN_THUMB_CACHE_SIZE, 1_000_000_000, 2
|
||||
) # High limit
|
||||
self.thumb_cache_size.setValidator(self.thumb_cache_size_validator)
|
||||
self.thumb_cache_size.setText(
|
||||
str(max(self.driver.settings.thumb_cache_size, MIN_THUMB_CACHE_SIZE)).removesuffix(".0")
|
||||
)
|
||||
self.thumb_cache_size_layout.addWidget(self.thumb_cache_size)
|
||||
self.thumb_cache_size_layout.setStretch(1, 2)
|
||||
self.thumb_cache_size_layout.addWidget(QLabel("MiB"))
|
||||
form_layout.addRow(
|
||||
Translations["settings.thumb_cache_size.label"], self.thumb_cache_size_container
|
||||
)
|
||||
|
||||
# Cached Thumbnail Resolution
|
||||
self.cached_thumb_res_container = QWidget()
|
||||
self.cached_thumb_res_layout = QHBoxLayout(self.cached_thumb_res_container)
|
||||
self.cached_thumb_res_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.cached_thumb_res_layout.setSpacing(6)
|
||||
self.cached_thumb_res = QLineEdit()
|
||||
self.cached_thumb_res.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
self.cached_thumb_res_validator = QIntValidator(MIN_CACHED_THUMB_RES, MAX_CACHED_THUMB_RES)
|
||||
self.cached_thumb_res.setValidator(self.cached_thumb_res_validator)
|
||||
self.cached_thumb_res.setText(str(self.driver.settings.cached_thumb_resolution))
|
||||
self.cached_thumb_res_layout.addWidget(self.cached_thumb_res)
|
||||
self.cached_thumb_res_layout.setStretch(1, 2)
|
||||
self.cached_thumb_res_layout.addWidget(QLabel("px"))
|
||||
form_layout.addRow(
|
||||
Translations["settings.cached_thumb_resolution.label"], self.cached_thumb_res_container
|
||||
)
|
||||
|
||||
def __build_localization_settings(self):
|
||||
form_layout = QFormLayout(self.localization_settings_container)
|
||||
form_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
# Language
|
||||
self.language_combobox = QComboBox()
|
||||
translated_langs = [(Translations[f"language.{lang}"], lang) for lang in LANGUAGES]
|
||||
translated_langs.sort(key=lambda x: x[0])
|
||||
|
||||
for lang in translated_langs:
|
||||
self.language_combobox.addItem(lang[0], lang[1])
|
||||
current_lang: str = self.driver.settings.language
|
||||
if current_lang not in LANGUAGES:
|
||||
current_lang = DEFAULT_TRANSLATION
|
||||
self.language_combobox.setCurrentIndex([x[1] for x in translated_langs].index(current_lang))
|
||||
self.language_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.language"], self.language_combobox)
|
||||
|
||||
# Date Format
|
||||
self.dateformat_combobox = QComboBox()
|
||||
@@ -303,20 +317,34 @@ class SettingsPanel(PanelWidget):
|
||||
self.zeropadding_checkbox.setChecked(self.driver.settings.zero_padding)
|
||||
form_layout.addRow(Translations["settings.zeropadding.label"], self.zeropadding_checkbox)
|
||||
|
||||
# TODO: Implement Library Settings
|
||||
def __build_library_settings(self): # pyright: ignore[reportUnusedFunction]
|
||||
form_layout = QFormLayout(self.library_settings_container)
|
||||
def __build_appearance_settings(self):
|
||||
form_layout = QFormLayout(self.appearance_settings_container)
|
||||
form_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
todo_label = QLabel("TODO")
|
||||
form_layout.addRow(todo_label)
|
||||
# Dark Mode
|
||||
self.theme_combobox = QComboBox()
|
||||
for k in SettingsPanel.theme_map:
|
||||
self.theme_combobox.addItem(SettingsPanel.theme_map[k], k)
|
||||
theme = self.driver.settings.theme
|
||||
if theme not in SettingsPanel.theme_map:
|
||||
theme = Theme.DEFAULT
|
||||
self.theme_combobox.setCurrentIndex(list(SettingsPanel.theme_map.keys()).index(theme))
|
||||
self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox)
|
||||
|
||||
def __get_language(self) -> str:
|
||||
return list(LANGUAGES.values())[self.language_combobox.currentIndex()]
|
||||
# Splash Screen
|
||||
self.splash_combobox = QComboBox()
|
||||
for k in SettingsPanel.splash_map:
|
||||
self.splash_combobox.addItem(SettingsPanel.splash_map[k], k)
|
||||
splash = self.driver.settings.splash
|
||||
if splash not in SettingsPanel.splash_map:
|
||||
splash = Splash.DEFAULT
|
||||
self.splash_combobox.setCurrentIndex(list(SettingsPanel.splash_map.keys()).index(splash))
|
||||
form_layout.addRow(Translations["settings.splash.label"], self.splash_combobox)
|
||||
|
||||
def get_settings(self) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
|
||||
return {
|
||||
"language": self.__get_language(),
|
||||
"language": self.language_combobox.currentData(),
|
||||
"open_last_loaded_on_startup": self.open_last_lib_checkbox.isChecked(),
|
||||
"generate_thumbs": self.generate_thumbs.isChecked(),
|
||||
"thumb_cache_size": max(
|
||||
|
||||
@@ -16,38 +16,38 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
DEFAULT_TRANSLATION = "en"
|
||||
|
||||
LANGUAGES = {
|
||||
# "Amharic": "am", # Minimal
|
||||
"Cebuano": "ceb",
|
||||
"Chinese (Simplified)": "zh_Hans",
|
||||
"Chinese (Traditional)": "zh_Hant",
|
||||
"Czech": "cs",
|
||||
# "Danish": "da", # Minimal
|
||||
"Dutch": "nl",
|
||||
"English": "en",
|
||||
"Filipino": "fil",
|
||||
"Finnish": "fi",
|
||||
"French": "fr",
|
||||
"German": "de",
|
||||
"Greek": "el",
|
||||
"Hungarian": "hu",
|
||||
# "Icelandic": "is", # Minimal
|
||||
"Italian": "it",
|
||||
"Japanese": "ja",
|
||||
"Norwegian Bokmål": "nb_NO",
|
||||
"Polish": "pl",
|
||||
"Portuguese (Brazil)": "pt_BR",
|
||||
"Portuguese (Portugal)": "pt",
|
||||
"Romanian": "ro",
|
||||
"Russian": "ru",
|
||||
"Spanish": "es",
|
||||
"Swedish": "sv",
|
||||
"Tamil": "ta",
|
||||
# "Thai": "th", # Minimal
|
||||
"Toki Pona": "tok",
|
||||
"Turkish": "tr",
|
||||
"Viossa": "qpv",
|
||||
}
|
||||
LANGUAGES = [
|
||||
# "am", # Minimal
|
||||
"ceb",
|
||||
"cs",
|
||||
# "da", # Minimal
|
||||
"de",
|
||||
"el",
|
||||
"en",
|
||||
"es",
|
||||
"fi",
|
||||
"fil",
|
||||
"fr",
|
||||
"hu",
|
||||
# "is", # Minimal
|
||||
"it",
|
||||
"ja",
|
||||
"nb_NO",
|
||||
"nl",
|
||||
"pl",
|
||||
"pt_BR",
|
||||
"pt",
|
||||
"qpv",
|
||||
"ro",
|
||||
"ru",
|
||||
"sv",
|
||||
"ta",
|
||||
# "th", # Minimal
|
||||
"tok",
|
||||
"tr",
|
||||
"zh_Hans",
|
||||
"zh_Hant",
|
||||
]
|
||||
|
||||
# A map of field class names to their respective translation keys.
|
||||
FIELD_TYPE_KEYS = {
|
||||
|
||||
+11
-12
@@ -59,7 +59,7 @@ from tagstudio.qt.controllers.field_template_search_panel_controller import Fiel
|
||||
from tagstudio.qt.controllers.fix_ignored_modal_controller import FixIgnoredEntriesModal
|
||||
from tagstudio.qt.controllers.ignore_modal_controller import IgnoreModal
|
||||
from tagstudio.qt.controllers.library_info_window_controller import LibraryInfoWindow
|
||||
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal, TagSearchPanel
|
||||
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal
|
||||
from tagstudio.qt.controllers.update_available_message_box import UpdateAvailableMessageBox
|
||||
from tagstudio.qt.global_settings import DEFAULT_GLOBAL_SETTINGS_PATH, GlobalSettings, Theme
|
||||
from tagstudio.qt.mixed.about_modal import AboutModal
|
||||
@@ -85,7 +85,6 @@ from tagstudio.qt.views.main_window import MainWindow
|
||||
from tagstudio.qt.views.panel_modal import PanelModal
|
||||
from tagstudio.qt.views.splash import SplashScreen
|
||||
from tagstudio.qt.views.stylesheets.stylesheets import header
|
||||
from tagstudio.qt.views.tag_search_panel_view import TagSearchPanelView
|
||||
|
||||
BADGE_TAGS = {
|
||||
BadgeType.FAVORITE: TAG_FAVORITE,
|
||||
@@ -352,15 +351,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.app.setDesktopFileName("tagstudio")
|
||||
|
||||
# Initialize the Tag Manager panel
|
||||
self.tag_manager_panel = PanelModal(
|
||||
widget=TagSearchPanel(
|
||||
self.lib,
|
||||
is_tag_chooser=False,
|
||||
view=TagSearchPanelView(is_tag_chooser=False),
|
||||
),
|
||||
self.tag_manager_panel = TagSearchModal(
|
||||
self.lib,
|
||||
title=Translations["tag_manager.title"],
|
||||
is_savable=False,
|
||||
is_tag_chooser=False,
|
||||
)
|
||||
self.tag_manager_panel.tsp.set_driver(self)
|
||||
|
||||
self.tag_manager_panel.done.connect(
|
||||
lambda checked=False: self.main_window.preview_panel.set_selection(
|
||||
self.selected, update_preview=False
|
||||
@@ -386,8 +383,10 @@ class QtDriver(DriverMixin, QObject):
|
||||
)
|
||||
)
|
||||
|
||||
# Initialize the Tag Search panel
|
||||
self.add_tag_modal = TagSearchModal(self.lib, is_tag_chooser=True)
|
||||
# Initialize the "Add Tag" panel
|
||||
self.add_tag_modal = TagSearchModal(
|
||||
self.lib, title=Translations["tag.add.plural"], is_tag_chooser=True
|
||||
)
|
||||
self.add_tag_modal.tsp.set_driver(self)
|
||||
self.add_tag_modal.tsp.item_chosen.connect(
|
||||
lambda chosen_tag: (
|
||||
@@ -859,7 +858,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.modal = PanelModal(
|
||||
panel,
|
||||
Translations["tag.new"],
|
||||
Translations["tag.add"],
|
||||
Translations["tag.create"],
|
||||
is_savable=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from PySide6.QtWidgets import QPushButton
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("Use QPushButton with 'catch warnings' to silence disconnect warnings instead.")
|
||||
class QPushButtonWrapper(QPushButton):
|
||||
"""Custom QPushButton wrapper.
|
||||
|
||||
This is a customized implementation of the PySide6 QPushButton that allows to suppress
|
||||
the warning that is triggered by disconnecting a signal that is not currently connected.
|
||||
"""
|
||||
|
||||
is_connected: bool
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.is_connected = False
|
||||
@@ -16,12 +16,10 @@ from PySide6.QtGui import (
|
||||
QPalette,
|
||||
QPen,
|
||||
)
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from tagstudio.qt.views.qbutton_wrapper import QPushButtonWrapper
|
||||
from PySide6.QtWidgets import QPushButton, QWidget
|
||||
|
||||
|
||||
class ThumbButton(QPushButtonWrapper):
|
||||
class ThumbButton(QPushButton):
|
||||
def __init__(self, parent: QWidget, thumb_size: tuple[int, int]) -> None:
|
||||
super().__init__(parent)
|
||||
self.thumb_size: tuple[int, int] = thumb_size
|
||||
|
||||
@@ -196,6 +196,36 @@
|
||||
"json_migration.title.new_lib": "<h2>v9.5+ Library</h2>",
|
||||
"json_migration.title.old_lib": "<h2>v9.4 Library</h2>",
|
||||
"landing.open_create_library": "Open/Create Library {shortcut}",
|
||||
"language.am": "Amharic",
|
||||
"language.ceb": "Cebuano",
|
||||
"language.cs": "Czech",
|
||||
"language.da": "Danish",
|
||||
"language.de": "German",
|
||||
"language.el": "Greek",
|
||||
"language.en": "English",
|
||||
"language.es": "Spanish",
|
||||
"language.fi": "Finnish",
|
||||
"language.fil": "Filipino",
|
||||
"language.fr": "French",
|
||||
"language.hu": "Hungarian",
|
||||
"language.is": "Icelandic",
|
||||
"language.it": "Italian",
|
||||
"language.ja": "Japanese",
|
||||
"language.nb_NO": "Norwegian Bokmål",
|
||||
"language.nl": "Dutch",
|
||||
"language.pl": "Polish",
|
||||
"language.pt": "Portuguese",
|
||||
"language.pt_BR": "Portuguese (Brazil)",
|
||||
"language.qpv": "Viossa",
|
||||
"language.ro": "Romanian",
|
||||
"language.ru": "Russian",
|
||||
"language.sv": "Swedish",
|
||||
"language.ta": "Tamil",
|
||||
"language.th": "Thai",
|
||||
"language.tok": "Toki Pona",
|
||||
"language.tr": "Turkish",
|
||||
"language.zh_Hans": "Chinese (Simplified)",
|
||||
"language.zh_Hant": "Chinese (Traditional)",
|
||||
"library_info.cleanup": "Cleanup",
|
||||
"library_info.cleanup.backups": "Library Backups:",
|
||||
"library_info.cleanup.dupe_files": "Duplicate Files:",
|
||||
@@ -277,6 +307,7 @@
|
||||
"select.all": "Select All",
|
||||
"select.clear": "Clear Selection",
|
||||
"select.inverse": "Invert Selection",
|
||||
"settings.appearance": "Appearance",
|
||||
"settings.cached_thumb_resolution.label": "Cached Thumbnail Resolution",
|
||||
"settings.clear_thumb_cache.title": "Clear Thumbnail Cache",
|
||||
"settings.dateformat.english": "English",
|
||||
@@ -293,6 +324,8 @@
|
||||
"settings.infinite_scroll": "Infinite Scrolling",
|
||||
"settings.language": "Language",
|
||||
"settings.library": "Library Settings",
|
||||
"settings.localization": "Localization",
|
||||
"settings.media": "Media",
|
||||
"settings.open_library_on_start": "Open Library on Start",
|
||||
"settings.page_size": "Page Size",
|
||||
"settings.restart_required": "Please restart TagStudio for changes to take effect.",
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user